操作线程的方法
操作线程有很多方法,这些方法可以使线程从某一种状态过渡到另一种状态。
线程的休眠
一种能控制线程行为的方法是调用 sleep() 方法,sleep() 方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位。在前面的实例中,已经演示过 sleep() 方法,它通常是在 run() 方法内的循环中被使用。sleep() 方法的语法如下:
try {
Thread.sleep(2000);
} catch(InterruptedException e) {
e.printStackTrace();
}
上述代码会使线程在 2 秒之内不会进入就绪状态。由于 sleep() 方法的执行有可能抛出 InterruptedException 异常,因此将 sleep() 方法的调用放在 try-catch 块中。虽然使用了 sleep() 方法的线程在一段时间内会醒来,但是并不能保证它醒来后进入运行状态,只能保证它进入就绪状态。
为了使读者更深入地了解线程的休眠方法,来看下面的实例。
【例20.3】每0.1秒绘制一条随机颜色的线条(实例位置:资源包\TM\sl\20\3)
在项目中创建 SleepMethodTest 类,该类继承 JFrame 类,实现在窗体中自动画线段的功能,并且为线段设置颜色,颜色是随机产生的。
import java.awt.*;
import java.util.Random;
import javax.swing.*;
public class SleepMethodTest extends JFrame {
private static Color[] color = { Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN, Color.ORANGE, Color.YELLOW,
Color.RED, Color.PINK, Color.LIGHT_GRAY }; // 定义颜色数组
private static final Random rand = new Random(); // 创建随机对象
private static Color getC() { // 获取随机颜色值的方法
return color[rand.nextInt(color.length)];
}
public SleepMethodTest() {
Thread t = new Thread(new Runnable() { // 创建匿名线程对象
int x = 30; // 定义初始坐标
int y = 50;
public void run() {
while (true) { // 无限循环
try {
Thread.sleep(100); // 线程休眠0.1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
Graphics graphics = getGraphics(); // 获取组件绘图上下文对象
graphics.setColor(getC()); // 设置绘图颜色
graphics.drawLine(x, y, 100, y++); // 绘制直线并递增垂直坐标
if (y >= 80) {
y = 50;
}
}
}
});
t.start(); // 启动线程
}
public static void main(String[] args) {
init(new SleepMethodTest(), 100, 100);
}
public static void init(JFrame frame, int width, int height) { // 初始化程序界面的方法
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
运行结果如图20.5所示。
在本实例中定义了 getC() 方法,该方法用于随机产生 Color 类型的对象,并且在产生线程的匿名内部类中使用 getGraphics() 方法获取 Graphics 对象,使用该对象调用 setColor() 方法为图形设置颜色。调用 drawLine() 方法绘制一条线段,同时线段会根据纵坐标的变化自动调整。

线程的加入
如果当前某程序为多线程程序,假如存在一个线程A,现在需要插入线程B,并要求线程B先执行完毕,然后继续执行线程A,此时可以使用 Thread 类中的 join() 方法来完成。这就好比此时读者正在看电视,突然有人上门收水费,读者必须付完水费后才能继续看电视。
当某个线程使用 join() 方法加入另一个线程时,另一个线程会等待该线程执行完毕后再继续执行。下面来看一个使用 join() 方法的实例。
【例20.4】让进度条A等待进度条B(实例位置:资源包\TM\sl\20\4)
在项目中创建 JoinTest 类,该类继承 JFrame 类。该实例包括两个进度条,进度条的进度由线程来控制,线程通过调用 join() 方法使上面的进度条必须等待下面的进度条完成后才可以继续。
import java.awt.BorderLayout;
import javax.swing.*;
public class JoinTest extends JFrame {
private Thread threadA; // 定义两个线程
private Thread threadB;
private JProgressBar progressBar = new JProgressBar(); // 定义两个进度条组件
private JProgressBar progressBar2 = new JProgressBar();
public static void main(String[] args) {
JoinTest test = new JoinTest();
test.setVisible(true);
}
public JoinTest() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(200, 200, 200, 100);
getContentPane().add(progressBar, BorderLayout.NORTH); // 将进度条设置在窗体最北面
getContentPane().add(progressBar2, BorderLayout.SOUTH); // 将进度条设置在窗体最南面
progressBar.setStringPainted(true); // 设置进度条显示数字字符
progressBar2.setStringPainted(true);
// 使用匿名内部类形式初始化Thread实例
threadA = new Thread(new Runnable() {
int count = 0;
public void run() { // 重写run()方法
while (true) {
progressBar.setValue(++count); // 设置进度条的当前值
try {
Thread.sleep(100); // 使线程A休眠100毫秒
threadB.join(); // 使线程B调用join()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
threadA.start(); // 启动线程A
threadB = new Thread(new Runnable() {
int count = 0;
public void run() {
while (true) {
progressBar2.setValue(++count); // 设置进度条的当前值
try {
Thread.sleep(100); // 使线程B休眠100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 100) // 当count变量增长为100时
break; // 跳出循环
}
}
});
threadB.start(); // 启动线程B
}
}
运行结果如图20.6所示。

在本实例中同时创建了两个线程,这两个线程分别负责进度条的滚动。在线程 A 的 run() 方法中使线程 B 的对象调用 join() 方法,而 join() 方法使当前运行线程暂停,直到调用 join() 方法的线程执行完毕后再执行,所以线程 A 等待线程B执行完毕后再开始执行,即下面的进度条滚动完毕后上面的进度条才开始滚动。
线程的中断
以往有的时候会使用 stop() 方法停止线程,但当前版本的 JDK 早已废除了 stop() 方法,不建议使用 stop() 方法来停止一个线程的运行。现在提倡在 run() 方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。
如果线程是因为使用了 sleep() 或 wait() 方法进入了就绪状态的,那么可以使用 Thread 类中 interrupt() 方法使线程离开 run() 方法,同时结束线程,但程序会抛出 InterruptedException 异常,用户可以在处理该异常时完成线程的中断业务处理,如终止 while 循环。
下面的实例演示了某个线程使用 interrupted() 方法,同时程序抛出了 InterruptedException 异常,在异常处理时结束了 while 循环。在项目中,经常在这里执行关闭数据库连接和关闭 Socket 连接等操作。
【例20.5】单击按钮使进度条停止滚动(实例位置:资源包\TM\sl\20\5)
项目中创建 InterruptedSwing 类,该类实现 Runnable 接口,创建一个进度条,让进度条不断滚动。添加一个按钮,若用户单击该按钮,滚动条则停止滚动。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
public class InterruptedSwing extends JFrame {
public static void main(String[] args) {
init(new InterruptedSwing(), 100, 100);
}
public InterruptedSwing() {
JProgressBar progressBar = new JProgressBar(); // 创建进度条
getContentPane().add(progressBar, BorderLayout.NORTH); // 将进度条放置在窗体合适位置
JButton button = new JButton("停止");
getContentPane().add(button, BorderLayout.SOUTH);
progressBar.setStringPainted(true); // 设置进度条上显示数字
Thread t = new Thread(new Runnable() {
int count = 0;
public void run() {
while (true) {
progressBar.setValue(++count); // 设置进度条的当前值
try {
Thread.sleep(100); // 使线程休眠100毫秒
} catch (InterruptedException e) { // 捕捉InterruptedException异常
System.out.println("当前线程序被中断");
break;
}
}
}
});
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
t.interrupt(); // 中断线程
}
});
t.start(); // 启动线程
}
public static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
运行本实例后,单击按钮可以看到如图20.7所示的结果。
在本实例中,由于调用了 interrupted() 方法,因此抛出了 InterruptedException 异常。

线程的礼让
Thread 类中提供了一种礼让方法,使用 yield() 方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源进行礼让。
yield() 方法使具有同样优先级的线程有进入可执行状态的机会,在当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用 yield() 方法,因为操作系统会为线程自动分配 CPU 时间片来执行。
编程训练(答案位置:资源包\TM\sl\20\编程训练)
【训练3】模拟红绿灯变化场景 红灯亮8秒,绿灯亮5秒,黄灯亮2秒。
【训练4】龟兔赛跑 使用线程的加入模拟龟兔赛跑:兔子跑到70米时,开始睡觉;乌龟爬至终点时,兔子醒了跑至终点。