我們在使用計算的時候會感受到計算機好像在同時執行很多工,這也是我最初接觸計算機給我留下的印象,而我們普通人在同一時刻大腦只能思考一件事情(當然不排除一些異能者能夠做到一心二用),而且我們在思考完一件事情之後進入另一件事情的思考需要花費一段時間適應。而對於計算機來說,其執行任務間的切換是相當快的,以前計算機還是單CPU的時候就是通過這種在各種任務之間的快速切換而“偽實現”了同時執行任務。隨著硬體飛速發展,計算機配備了多CPU晶片,就在真正意義上實現了多執行緒,實現了同時執行多種任務。
代號為“Prodigy”的64位多核、多執行緒處理器
然而萬事萬物都是有利有弊,多執行緒在為應用帶來效能提高的同時,也帶來了新的問題。因為多執行緒共享記憶體變數/物件,且可以同時修改同一個記憶體變數/物件,那麼就可以產生訪問衝突,從而造成資料不一致,這就是所謂的執行緒安全性問題。因執行緒安全處理不當造成的程式錯誤,其錯誤現象經常表現得比較隨機不可確定,難以發現規律。因此,在Debug時很有挑戰性。先前在《執行緒的六個狀態以及其安全性問題的個例解析》《程式設計中如何避免死鎖問題的發生?》在學習多執行緒的時候,除了理解執行緒執行原理或者機制和執行緒管理的基本方法外,如果保證執行緒安全也是多執行緒的一種重點。
下面我通過一個單執行緒的程式和改進之後的多執行緒程式用圖形介面的方式將單執行緒與多執行緒的區別簡單展示一下
注意: 將Ball.java; BallComponent.java; Bounce.java; BounceThread.java四個Java檔案放置在同一檔案目錄下,全部編譯之後,分別執行Bounce.java和BounceThread.java即可觀察到效果。
強烈建議各位朋友們玩一下,尤其是對多執行緒的概念比較模糊的朋友們,比較Bounce和BounceThread的區別,對理解和認識多執行緒會起到一定的幫助。
Ball.java
import java.awt.geom.*;
public class Ball {
private static final int XSIZE = 15;
private static final int YSIZE = 15;
private double x = 0;
private double y = 0;
private double dx = 1;
private double dy = 1;
public void move(Rectangle2D bounds) {
x += dx;
y += dy;
if(x < bounds.getMinX()) {
x = bounds.getMinX();
dx = -dx;
}
if(x + XSIZE >= bounds.getMaxX()) {
x = bounds.getMaxX() - XSIZE;
dx = -dx;
}
if(y < bounds.getMinY()) {
y = bounds.getMinY();
dy = -dy;
}
if(y + YSIZE >= bounds.getMaxY()) {
y = bounds.getMaxY() - YSIZE;
dy = -dy;
}
}
public Ellipse2D getShape() {
return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
}
}
BallComponent.java
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class BallComponent extends JPanel {
private ArrayList<Ball> balls = new ArrayList<Ball>();
public void add(Ball b) {
balls.add(b);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for(Ball b : balls) {
g2.fill(b.getShape());
}
}
}
Bounce.java 單執行緒程式
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**
* 實現功能: 建立一個使用者介面, 點選按鈕start產生一個小黑球做直線彈射運動, 直至某點停止運動
其中按一次start按鈕需要等到小黑球停止運動的時候才可以再按一次
*/
public class Bounce {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new BounceJFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
class BounceJFrame extends JFrame {
public static final int DEFAULT_WIDTH = 450;
public static final int DEFAULT_HEIGHT = 350;
public static final int STEPS = 1000;
public static final int DELAY = 3;
private BallComponent comp;
public BounceJFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setTitle("Bounce");
comp = new BallComponent();
add(comp, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", new ActionListener() {
public void actionPerformed(ActionEvent event) {
addBall();
}
});
addButton(buttonPanel, "Close", new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
});
add(buttonPanel, BorderLayout.SOUTH);
}
public void addButton(Container c, String title, ActionListener listener) {
JButton button = new JButton(title);
c.add(button);
button.addActionListener(listener);
}
public void addBall() {
try {
Ball ball = new Ball();
comp.add(ball);
for(int i = 1; i <= STEPS; i++) {
ball.move(comp.getBounds());
comp.paint(comp.getGraphics());
Thread.sleep(DELAY);
}
} catch(InterruptedException e) {}
}
}
BounceThread.java 多執行緒程式
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/*
* 實現功能: 與Bounce.java不同的是, 這個程式按下start按鈕建立一個小黑球運動之後可以立即再次按下按鈕建立一個小黑球
*
*/
public class BounceThread {
public static void main(String[] args0) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame frame = new BounceJFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
class BallRunnable implements Runnable {
private Ball ball;
private Component component;
public static final int STEPS = 1000;
public static final int DELAY = 5;
public BallRunnable(Ball aBall, Component aComponent) {
ball = aBall;
component = aComponent;
}
public void run() {
try {
for(int i = 1; i <= STEPS; i++) {
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
} catch(InterruptedException e) {}
}
}
class BounceJFrame extends JFrame {
private BallComponent comp;
public static final int DEFAULT_WIDTH = 450;
public static final int DEFAULT_HEIGHT = 350;
public static final int STEPS = 1000;
public static final int DELAY = 3;
public BounceJFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setTitle("BounceThread");
comp = new BallComponent();
add(comp, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", new ActionListener() {
public void actionPerformed(ActionEvent event) {
addBall();
}
});
addButton(buttonPanel, "Close", new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.exit(0);
}
});
add(buttonPanel, BorderLayout.SOUTH);
}
/*
*
*
*/
public void addButton(Container c, String title, ActionListener listener) {
JButton button = new JButton(title);
c.add(button);
button.addActionListener(listener);
}
public void addBall() {
Ball ball = new Ball();
comp.add(ball);
Runnable r = new BallRunnable(ball, comp);
Thread t = new Thread(r);
t.start();
}
}
下圖是兩個程式各自的執行截圖,其中左圖為單執行緒 右圖為多執行緒
通過簡單觀察可以看到左右兩圖之間的區別,左圖中只有小黑球到達終點時,按鈕才回彈回來,這就是單執行緒。
多執行緒作為Java的核心內容之一,作為程式效能提升的基本途徑。想要充分呼叫計算資源,我們還需要在程式設計的時候避免過於複雜的設計,特別是對於初學者而言,學習多執行緒就好像進入了一個全新的領域,通過學習多執行緒的思維方式,對我們思維的擴充套件還是有一定好處的。