- 什么是多线程
- 中断线程
- 线程状态
- 线程属性
- 同步
- 阻塞队列
- 线程安全的集合
- Callable和Future
- 执行器
- 同步器
- 线程和Swing
有两种实现多任务的方法,这取决于操作系统在中断程序时的行为——直接中断而不需要实现和被中断程序协商,还是只有在被中断程序同意并愿意交出控制权之后才能执行中断。前者称为枪战时多任务;后者称为协作(非抢占式)多任务。早期的操作系统,像Windows 3.x和Mac OS 9是协作多任务系统,一些简易设备(例如手机上的操作系统也采用这种方式)。Unix/Linux、Window NT/XP(以及针对32位程序的Windows9X)和OS X则是抢占式的。抢占式多任务更加有效,但实现起来难度较大。而在协作多任务机制下,一个行为不当的程序可能会独占所有资源,导致其它所有程序无法正常工作。
[Note:抢占式和协作式]
多线程程序在更低的层次中引入多任务从而扩展了多任务的思想: 单个程序看起来可以同时处理多个任务。通常对每个任务成为一个线程,它是控制线程的简称。可以一次运行多个线程的程序被称为是多线程的。
那么,多线程和多进程有什么区别呢?本质的区别在于每个进程有它自己的变量的完备集,线程则共享相同的数据。这听起来似乎有些危险,事实上也确实如此,你将会在本章的后面的内容中看到这个问题。 [Note:线程共享数据带来的问题] 尽管如此,对程序来说,共享的变量是县城之间的通信比进程间的通信更加简单而有效。而且,对于某些操作系统而言,线程比进程更“轻量级”,创建和销毁单个线程比发起进程的开销要小得多。
在实际使用中,多线程非常有用。例如,一个浏览器必须能够同时下载多幅图片;一个Web服务器需要能够处理并发的请求;Java程序设计语言自身就使用了一个线程在后台进行垃圾回收,使你不用为内存管理而操心!图形用户界面(GUI)程序用一个单独的线程从主机操作环境中收集用户界面事件。本章将为你展示如何为你的Java应用程序添加多线程功能。
在JDK5.0中,多线程发生了重大变化,增加了大量的类和接口,他们为大部分程序员都需要的多线程机制提供了高质量的实现。在本章中,我们会解释JDK5.0的新特性以及一些经典的同步机制,并告诉教你如何在这些机制中进行选择。
注意:多线程可能会变得非常复杂。在本章中,我们覆盖了程序员可能会需要的所有工具。尽管如此,对于更复杂的系统级编程,我们建议参考更高级的资料,例如《Concurrent Programming in Java》,该书由Doug Lea撰写,由Addison-Wesley在1999年出版。
1.1 什么是线程
让我们先来看一个不使用多线程的程序,用户很难让它去执行多个任务。对其进行剖析之后,我们将向你展示让这个程序运行彼此独立的多个线程是多么的简单。这个程序动画显示了一个跳动的球,方法是不断地移动它,确定它是否从墙壁弹回,然后刷新它。
一旦按下Start按钮,程序将从屏幕的左上角弹射出一个球,这个球便开始弹跳。Start按钮的处理程序调用addBall方法。这个方法包含一个运行1000次move的循环。每次调用move时,球会被移动一点点,如果它从墙上被反弹回来就要调整移动的方向,然后刷新面板。
Ball ball = new Ball();
panel.add(ball);
for(int i = 1; i <= STEPS; i++){
ball.move(panel.getBounds());
panel.paint(panel.getGraphics());
Tread.sleep(DELAY);
}
Thread类的静态的sleep方法会使线程停止指定的毫秒数。
调用Thread.sleep不会创建新的线程,sleep只是Thread类的一个静态方法,用于暂时停止当前线程的活动。
sleep方法可以抛出一个InterruptedException。我们会在稍后讨论这个异常和处理它的方法。而现在,如果这个异常发生了,我们只终止弹跳。
如果你运行这个程序,球能够很好地到处跳跃,但是他接管了整个应用。如果你在它结束1000次移动前感到厌倦了,并点击关闭按钮,就会发现球仍旧在继续跳跃。在球自己结束跳跃之前你无法与程序交互。
注意:如果你仔细地看过了本节末尾的代码,你将会注意到在Ball类的move方法中有以下这样一个调用。
panel.paint(panel.getGraphics())
这一点很奇怪,一般来讲,你应该调用repaint,让AWT来获取图形上下文并负责绘制。但是如果在该程序中你试图调用panel.repaint(),你会发现面板永远不会被刷新。因为addBall方法已经完全接管了所有的处理。在后面一个使用独立线程来计算球位置的程序中,我们会重新使用大家所熟悉的repaint。
显然,这个程序的性能相当糟糕。你肯定不希望你所使用的程序在处理一件费时的工作时会以则何种方式来运行。毕竟,当你通过网络连接读取数据时,其他任务被阻塞是经常发生的,有时候你的确想要中断读取操作。例如,假设你在下载一副很大的图片,当你看了一部分后,你决定不需要或不想看余下的部分了,你当然希望能够点击停止或后退按钮来中断加载过程。在下一节中,我们会演示如何通过在一个独立的线程中运行代码的关键部分,来保持用户对程序的控制。
让我们先来看一个不使用多线程的程序,用户很难让它去执行多个任务。对其进行剖析之后,我们将向你展示让这个程序运行彼此独立的多个线程是多么的简单。这个程序动画显示了一个跳动的球,方法是不断地移动它,确定它是否从墙壁弹回,然后刷新它。
一旦按下Start按钮,程序将从屏幕的左上角弹射出一个球,这个球便开始弹跳。Start按钮的处理程序调用addBall方法。这个方法包含一个运行1000次move的循环。每次调用move时,球会被移动一点点,如果它从墙上被反弹回来就要调整移动的方向,然后刷新面板。
Ball ball = new Ball();
panel.add(ball);
for(int i = 1; i <= STEPS; i++){
ball.move(panel.getBounds());
panel.paint(panel.getGraphics());
Tread.sleep(DELAY);
}
Thread类的静态的sleep方法会使线程停止指定的毫秒数。
调用Thread.sleep不会创建新的线程,sleep只是Thread类的一个静态方法,用于暂时停止当前线程的活动。
sleep方法可以抛出一个InterruptedException。我们会在稍后讨论这个异常和处理它的方法。而现在,如果这个异常发生了,我们只终止弹跳。
如果你运行这个程序,球能够很好地到处跳跃,但是他接管了整个应用。如果你在它结束1000次移动前感到厌倦了,并点击关闭按钮,就会发现球仍旧在继续跳跃。在球自己结束跳跃之前你无法与程序交互。
注意:如果你仔细地看过了本节末尾的代码,你将会注意到在Ball类的move方法中有以下这样一个调用。
panel.paint(panel.getGraphics())
这一点很奇怪,一般来讲,你应该调用repaint,让AWT来获取图形上下文并负责绘制。但是如果在该程序中你试图调用panel.repaint(),你会发现面板永远不会被刷新。因为addBall方法已经完全接管了所有的处理。在后面一个使用独立线程来计算球位置的程序中,我们会重新使用大家所熟悉的repaint。
显然,这个程序的性能相当糟糕。你肯定不希望你所使用的程序在处理一件费时的工作时会以则何种方式来运行。毕竟,当你通过网络连接读取数据时,其他任务被阻塞是经常发生的,有时候你的确想要中断读取操作。例如,假设你在下载一副很大的图片,当你看了一部分后,你决定不需要或不想看余下的部分了,你当然希望能够点击停止或后退按钮来中断加载过程。在下一节中,我们会演示如何通过在一个独立的线程中运行代码的关键部分,来保持用户对程序的控制。
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Bounce {
public static void main(String[] argsj){
JFrame frame = new BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class Ball
{
private static final double XSIZE = 10;
private static final double YSIZE = 10;
private double dx = 1;
private double dy = 1;
private double x = 0;
private double y = 0;
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);
}
}
class BallPanel 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());
}
}
}
class BounceFrame extends JFrame{
private static final int DEFAULT_HEIGHT = 350;
private static final int DEFAULT_WIDTH = 450;
private static final int STEPS = 1000;
private static final long DELAY = 3;
private BallPanel panel;
public BounceFrame(){
setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
setTitle("Bounce");
panel = new BallPanel();
add(panel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel,"Start",
new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
addBall();
}
});
addButton(buttonPanel,"Close",
new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
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();
panel.add(ball);
for(int i = 1; i <= STEPS; i++){
ball.move(panel.getBounds());
panel.paint(panel.getGraphics());
Thread.sleep(DELAY);
}
}catch(InterruptedException ie){
ie.printStackTrace();
}
}
}
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Bounce {
public static void main(String[] argsj){
JFrame frame = new BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class Ball
{
private static final double XSIZE = 10;
private static final double YSIZE = 10;
private double dx = 1;
private double dy = 1;
private double x = 0;
private double y = 0;
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);
}
}
class BallPanel 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());
}
}
}
class BounceFrame extends JFrame{
private static final int DEFAULT_HEIGHT = 350;
private static final int DEFAULT_WIDTH = 450;
private static final int STEPS = 1000;
private static final long DELAY = 3;
private BallPanel panel;
public BounceFrame(){
setSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
setTitle("Bounce");
panel = new BallPanel();
add(panel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel,"Start",
new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
addBall();
}
});
addButton(buttonPanel,"Close",
new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
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();
panel.add(ball);
for(int i = 1; i <= STEPS; i++){
ball.move(panel.getBounds());
panel.paint(panel.getGraphics());
Thread.sleep(DELAY);
}
}catch(InterruptedException ie){
ie.printStackTrace();
}
}
}
API:java.lang.Thread 1.0
- static void sleep(long millis) 休眠指定毫秒数。 参数:millis 休眠的毫秒数
我们将通过在一个独立线程中运行移动球的代码来提高弹跳球程序的响应能力。事实上,你将能够弹射多个球。每一个都在他们自己的线程中运行。另外,AWT的事件分派线程将一直并行的运行。以处理用户界面事件。由于每个线程都有机会得以运行,所以,当用户在球弹跳的过程中点击关闭按钮的时候,主线程就有机会注意到它。随后线程就能执行“关闭”动作。
下面是在单独线程中运行一个任务的简单过程:
(1)将任务处理代码移到实现了Runnable接口的类的run方法中。这个接口非常简单,只有一个方法:
public interface Runnable{
void run();
}
你只要像这样实现一个类:
class MyRunnable implements Runnable
{
public void run(){
task code;
}
}
(2)创建你的类的一个对象
Runnable r = new MyRunnable();
(3)由Runnable创建你的类的一个对象:
Thread t = new Thread(r);
(4)启动线程。
t.start();
为了把我们的弹跳球放到独立线程中,我们只需要实现一个BallRunnable类并将动画部分的代码放到run方法中即可,就像下面的代码这样:
class BallRunnable implements Runnable{
...
public void run(){
try{
for(int i=1;i<= STEPS; i++){
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
}catch(InterruptedException ){
}
}
...
}
我们仍然需要捕获sleep方法所生命可能会跑出的InterruptedException异常。我们下一节讨论这个异常。一般情况下,线程将在被中断时终止。所以,当发生InterruptedException 异常时,我们的run方法会退出。现在不管什么时候按下开始按钮,addBall方法都会启动一个新的线程。
Ball b = new Ball();
panel.add(b);
Runnable r = new BallRunnable(b,panel);
Thread t = new Thread(r);
t.start();
这个问题现在已经讲得差不多了。你现在知道如何并行运行多个任务了。本章余下的内容将告诉你如何控制线程之间的交互。
例1-2展示了完整的代码。
注意:你也可以通过构建一个Thread的子类来定义一个线程,如下所示:
class MyTread extends Thread
{
public void run()
{
task code
}
}
然后构造一个子类的对象并调用它的start方法,然而我们已经不建议你这样做了。应该尽量从机制上减少需要并行任务的数量。如果你有很多任务,为每一个任务都创建一个独立线程的代价就太大了,此时你可以使用线程池。
警告:不要调用Thread类或Runnable对象的run方法。直接调用run方法只会在当前线程中执行任务,并不会启动新的线程,正确的做法是调用Thread.start方法,它会创建一个新的线程来执行run方法。
package best;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BounceThread {
public static void main(String[] argsj) {
JFrame frame = new BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class BallRunnable implements Runnable {
private static final int STEPS = 1000;
private static final long DELAY = 5;
private Component component;
private Ball ball;
public BallRunnable(Ball aBall, Component aComponent) {
ball = aBall;
component = aComponent;
}
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 1; i <= STEPS; i++) {
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
class Ball
{
private static final double XSIZE = 10;
private static final double YSIZE = 10;
private double dx = 1;
private double dy = 1;
private double x = 0;
private double y = 0;
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);
}
}
class BallPanel 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;
Color c = new Color(856023);
g2.setColor(c);
for (Ball b : balls) {
g2.fill(b.getShape());
}
}
}
class BounceFrame extends JFrame {
private static final int DEFAULT_HEIGHT = 350;
private static final int DEFAULT_WIDTH = 450;
private static final int STEPS = 1000;
private static final long DELAY = 3;
private BallPanel panel;
public BounceFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setTitle("Bounce");
panel = new BallPanel();
add(panel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
addBall();
}
});
addButton(buttonPanel, "Close", new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
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 b = new Ball();
panel.add(b);
Runnable r = new BallRunnable(b,panel);
Thread t = new Thread(r);
t.start();
}
}
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class BounceThread {
public static void main(String[] argsj) {
JFrame frame = new BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class BallRunnable implements Runnable {
private static final int STEPS = 1000;
private static final long DELAY = 5;
private Component component;
private Ball ball;
public BallRunnable(Ball aBall, Component aComponent) {
ball = aBall;
component = aComponent;
}
public void run() {
// TODO Auto-generated method stub
try {
for (int i = 1; i <= STEPS; i++) {
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
class Ball
{
private static final double XSIZE = 10;
private static final double YSIZE = 10;
private double dx = 1;
private double dy = 1;
private double x = 0;
private double y = 0;
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);
}
}
class BallPanel 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;
Color c = new Color(856023);
g2.setColor(c);
for (Ball b : balls) {
g2.fill(b.getShape());
}
}
}
class BounceFrame extends JFrame {
private static final int DEFAULT_HEIGHT = 350;
private static final int DEFAULT_WIDTH = 450;
private static final int STEPS = 1000;
private static final long DELAY = 3;
private BallPanel panel;
public BounceFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
setTitle("Bounce");
panel = new BallPanel();
add(panel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
addBall();
}
});
addButton(buttonPanel, "Close", new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
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 b = new Ball();
panel.add(b);
Runnable r = new BallRunnable(b,panel);
Thread t = new Thread(r);
t.start();
}
}




API java.lang.Thread 1.0
- Thread(Runnable target)构造一个新的线程来调用指定target的run( )方法。
- void start( ) 启动这个线程,将引发调用run( )方法。这个方法将立即返回,并且新线程将并发运行。
- void run( ) 调用关联Runnable的run方法。
- void run( ) 你必须重载这个方法,并且在这个方法中为你想要执行的任务提供相关的处理代码。