操作线程的方法

操作线程有很多方法,这些方法可以使线程从某一种状态过渡到另一种状态。

线程的休眠

一种能控制线程行为的方法是调用 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() 方法绘制一条线段,同时线段会根据纵坐标的变化自动调整。

image 2024 03 06 11 23 03 777
Figure 1. 图20.5 线程的休眠

线程的加入

如果当前某程序为多线程程序,假如存在一个线程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所示。

image 2024 03 06 11 25 07 285
Figure 2. 图20.6 使用join()方法控制进度条的滚动

在本实例中同时创建了两个线程,这两个线程分别负责进度条的滚动。在线程 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 异常。

image 2024 03 06 11 28 22 413
Figure 3. 图20.7 线程的中断

线程的礼让

Thread 类中提供了一种礼让方法,使用 yield() 方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源进行礼让。

yield() 方法使具有同样优先级的线程有进入可执行状态的机会,在当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用 yield() 方法,因为操作系统会为线程自动分配 CPU 时间片来执行。

编程训练(答案位置:资源包\TM\sl\20\编程训练)

【训练3】模拟红绿灯变化场景 红灯亮8秒,绿灯亮5秒,黄灯亮2秒。

【训练4】龟兔赛跑 使用线程的加入模拟龟兔赛跑:兔子跑到70米时,开始睡觉;乌龟爬至终点时,兔子醒了跑至终点。