我们在使用计算的时候会感受到计算机好像在同时执行很多任务,这也是我最初接触计算机给我留下的印象,而我们普通人在同一时刻大脑只能思考一件事情(当然不排除一些异能者能够做到一心二用),而且我们在思考完一件事情之后进入另一件事情的思考需要花费一段时间适应。而对于计算机来说,其执行任务间的切换是相当快的,以前计算机还是单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();
}
}
下图是两个程序各自的运行截图,其中左图为单线程 右图为多线程
该程序引用自Cary Cornell与Cay S.Horstmann著的《Java核心技术卷一:基础知识》
通过简单观察可以看到左右两图之间的区别,左图中只有小黑球到达终点时,按钮才回弹回来,这就是单线程。
多线程作为Java的核心内容之一,作为程序性能提升的基本途径。想要充分调用计算资源,我们还需要在编程的时候避免过于复杂的设计,特别是对于初学者而言,学习多线程就好像进入了一个全新的领域,通过学习多线程的思维方式,对我们思维的扩展还是有一定好处的。