1 . 线程的休眠。
一种能控制线程行为的方法是调用sleep()方法,sleep()方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位。在前面的实例中已经演示过sleep()方法,它通常是在run()方法内的循环中被使用。
sleep()方法的语法如下:
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
以上代码会是线程在2秒之内不会进入就绪状态,由于sleep()方法的执行有可能抛出InterruptedException异常,所以将sleep方法的调用放在try-catch块中。虽然使用了sleep()方法的线程在一段时间内会醒来,但是并不能保证它醒来后进入运行状态,只能保证它进入就绪状态。
import javax.swing.*;
import java.awt.*;
import java.util.Random;
public class SleepMethodTest extends JFrame {
/*
在项目中创建SleepMethodTest类,该类继承了JFrame类,实现在窗体中自动画线段的功能,并且为线段设置颜色,颜色是随机产生的。
*/
// 1 . 声明线程对象。
private Thread t;
// 2 . 定义颜色数组。
private static Color[] color = {
Color.BLACK , Color.BLUE , Color.CYAN , Color.PINK , Color.GREEN , Color.RED
};
// 3 . 创建随机对象。
private static final Random rand = new Random();
// 4 . 获取随机颜色值的方法。
private static Color getC(){
return color[rand.nextInt(color.length)];
}
public SleepMethodTest(){
// 5 . 创建匿名线程对象。
t = new Thread(new Runnable() {
// 6 . 定义初始坐标。
int x = 30;
int y = 50;
// 7 . 覆盖线程接口方法。
@Override
public void run() {
while (true){
try{
// 8 . 线程休眠100ms。
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
// 9 . 获取组件绘图上下文对象。
Graphics graphics = getGraphics();
// 10 . 设置绘图颜色。
graphics.setColor(getC());
// 11 . 绘制直线并递增垂直坐标。
graphics.drawLine(x,y,100,y++);
if(y >= 80){
y = 50;
}
}
}
});
// 12 . 启动线程。
t.start();
}
public static void main(String[] args) {
init(new SleepMethodTest(),100,100);
}
// 13 . 初始化程序界面的方法。
private static void init(JFrame frame, int width, int height) {
// 14 . 设置窗体的关闭方式。
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
在本实例中定义了getC()方法,该方法用于随机产生Color类型的对象,并且在产生线程的匿名内部类中使用getGraphics()方法获取Graphics对象,使用该对象调用setColor()方法为图形设置颜色;调用drawLine()方法绘制一条线段,同时线段会根据纵坐标的变化自动调整。
2 . 线程的加入。
如果当前某程序为多线程程序,加入存在一个线程A,现在需要插入线程B,并要求线程B先执行完毕,然后再继续执行线程A,此时可以使用Thread类中的join()方法来完成。这就好比此时读者正在看电视,突然有人上门收水费,读者必须付完水费后才能继续看电视。
当某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行。
import javax.swing.*;
import java.awt.*;
public class JoinTest extends JFrame {
/*
在项目中创建JoinTest类,该类继承了JFrame类。该实例包括两个进度条,进度条的进度有现成来控制,
通过使用join()方法使上面的进度条必须等下面的进度条完成后才可以继续。
*/
// 1 . 定义两个线程。
private Thread threadA;
private Thread threadB;
// 2 . 定义两个进度条组件。
final JProgressBar progressBar = new JProgressBar();
final JProgressBar progressBar2 = new JProgressBar();
int count = 0;
public JoinTest(){
super();
// 3 . 将进度条1设置在窗体最北面。
getContentPane().add(progressBar, BorderLayout.NORTH);
// 4 . 将进度条2设置在窗体最南面。
getContentPane().add(progressBar2, BorderLayout.SOUTH);
// 5 . 设置进度条显示数字字符。
progressBar.setStringPainted(true);
progressBar2.setStringPainted(true);
// 6 . 使用匿名内部类形式初始化Thread实例。
threadA = new Thread(new Runnable() {
int count = 0;
// 7 . 重写run()方法。
@Override
public void run() {
while(true){
// 8 . 设置进度条的当前值。
progressBar.setValue(++count);
try{
// 9 . 使线程A休眠100ms。
Thread.sleep(100);
// 10 . 使线程B调用join()方法。
threadB.join();
}catch (Exception e){
e.printStackTrace();
}
}
}
});
// 11 . 启动线程A。
threadA.start();
// 12 . 使用匿名内部类形式初始化Thread实例。
threadB = new Thread(new Runnable() {
int count = 0;
// 13 . 重写run()方法。
@Override
public void run(){
while (true){
// 14 . 设置进度条的当前值。
progressBar2.setValue(++count);
try{
// 15 . 使线程B休眠100ms。
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
// 16 . 设置count变量跳出循环条件。
if(count == 100){
break;
}
}
}
});
// 17 . 启动线程B。
threadB.start();
}
public static void main(String[] args) {
init(new JoinTest(),100,100);
}
// 18 . 设置窗体各种属性的方法。
public static void init(JFrame frame,int width,int height){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
在本实例中同时创建了两个线程,这两个线程分别负责进度条的滚动。在线程A的run()方法中使线程B的对象调用join()方法,而join()方法使当前运行线程暂停,直到调用join()方法的线程执行完毕后在执行,所以线程A等待线程B执行完毕后再开始执行,即下面的进度条滚动完毕后上面的进度条才开始滚动。
3 . 线程的中断。
如果线程是因为使用了sleep()或wait()方法进入了就绪状态,可以使用Thread类中的interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出InterruptedException异常,用户可以在处理该异常时完成线程的中断业务处理,如终止while循环。
下面的实例演示了某个线程使用interrupted()方法,同时程序抛出了InterruptedException异常,在异常处理时结束了while循环。在项目中,经常在这里执行关闭数据库链接和关闭Socket链接等操作。
import javax.swing.*;
import java.awt.*;
public class InterruptedSwing extends JFrame {
/*
在项目中创建了InterruptedSwing类,该类实现了Runnable接口,创建一个进度条,在表示进度条的线程中使用interrupted()方法。
*/
// 1 . 创建线程对象。
Thread thread;
public InterruptedSwing(){
super();
// 2 . 创建进度条。
final JProgressBar progressBar = new JProgressBar();
// 3 . 将进度条放置在窗体合适位置。
getContentPane().add(progressBar, BorderLayout.NORTH);
// 4 . 设置进度条上显示数字。
progressBar.setStringPainted(true);
// 5 . 使用匿名内部类形式初始化Thread实例。
thread = new Thread(new Runnable() {
int count = 0;
// 6 . 重写run()方法。
@Override
public void run() {
while (true){
// 7 . 设置进度条的当前值。
progressBar.setValue(++count);
try{
// 8 . 使线程休眠1000ms。
Thread.sleep(1000);
}catch (InterruptedException e){
// 9 . 捕捉InterruptedException异常。
System.out.println("当前程序被中断。");
break;
}
}
}
});
// 10 . 启动线程。
thread.start();
// 11 . 中断线程。
thread.interrupt();
}
public static void main(String[] args) {
init(new InterruptedSwing(),100,100);
}
private static void init(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
}
在本实例中,由于调用了Interrupted()方法,所以抛出了InterruptedException异常。
4 . 线程的礼让。
Thread类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
yield()方法使具有同样优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态,对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。
5 . 线程安全。
在单线程程序中,每次只能做一件事情,后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,如两个人同时说话、两个人同时过同一个度米乔等。所以在多线程编程中需要防止这些资源访问的冲突、Java提供了线程同步的机制来防止资源访问的冲突。
实际开发中,使用多线程程序的情况很多,如银行排号系统、火车站售票系统等。用售票系统解释,在代码中判断当前票数是否大于0,如果大于0则执行将票出售给顾客的 功能,但当两个线程同时访问这段代码时(假如这时只剩一张票),第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得出票数大于0的结论,于是它也执行售出操作,这样就会产生负数。所以在编写多线程程序时,应该考虑到线程安全问题,实质上线程安全问题来源于两个线程同时存取单一对象的数据。
public class ThreadSafeTest implements Runnable{
/*
在项目中创建ThreadSafeTest类,该类实现了Runnable接口,主要实现模拟火车站售票系统的功能。
*/
// 1 . 设置当前总票数。
int num = 10;
// 2 . 重写run()方法。
@Override
public void run() {
while (true) {
// 3 . 判断票数是否大于0.
if (num > 0) {
try {
// 4 . 使线程休眠100ms。
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Tickets " + num--);
}
}
}
public static void main(String[] args) {
// 5 . 实例化对象。
ThreadSafeTest threadSafeTest = new ThreadSafeTest();
// 6 . 以该类对象分别实例化4个线程。
Thread tA = new Thread(threadSafeTest);
Thread tB = new Thread(threadSafeTest);
Thread tC = new Thread(threadSafeTest);
Thread tD = new Thread(threadSafeTest);
// 7 . 分别启动线程。
tA.start();
tB.start();
tC.start();
tD.start();
}
}
最后打印剩下的票为负值,这样就出现了问题。这是由于同时创建了4个线程,这4个线程在执行run()方法,在num变量为1时,线程1、线程2、线程3、线程4都对num变量有存储功能,当线程1执行run()方法时,还没有来得及做递减操作,就指定它调用sleep()方法进入就绪状态,这时线程2、线程3和线程4都进入了run()方法,发现num变量依然大于0,但此时线程1休眠时间已到,将num变量值递减,同时线程2、线程3、线程4也都对num变量进行递减操作。从而产生负值。
6 . 线程同步机制。
基本上所有解决多线程资源冲突问题的方法都是采用给定时间只允许一个线程访问共享资源,这时就需要给共享资源上一道锁。这就好比一个人上洗手间时,他进入洗手间后会将门锁上,出来时再将锁打开,其他人才可以进入。
同步块-----在Java中提供了同步机制,可以有效防止资源冲突。同步机制使用synchronized关键字。
public class ThreadSafeTest2 implements Runnable{
/*
在本实例中,创建类ThreadSafeTest.java,在该类中修改run()方法,把对num的操作的代码设置在同步块中。
*/
// 1 . 设置票数总量。
int num = 10;
// 2 . 重写run()方法。
@Override
public void run() {
while (true){
// 3 . 同步块。
synchronized (""){
// 4 . 判断余票是否大于0.
if (num > 0){
try{
// 5 . 使线程休眠1000ms。
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("Tickets " + --num);
}
}
}
}
public static void main(String[] args) {
// 6 . 实例化例对象。
ThreadSafeTest2 t2 = new ThreadSafeTest2();
// 7 . 以该类对象分别实例化4个线程。
Thread tA = new Thread(t2);
Thread tB = new Thread(t2);
Thread tC = new Thread(t2);
Thread tD = new Thread(t2);
// 8 . 分别启动线程。
tA.start();
tB.start();
tC.start();
tD.start();
}
}
打印到最后没有出现负票数,这是因为将资源放置在了同步块中。这个同步块也被称为临界区,它使用synchronized关键字建立。
synchronized(Object){
}
通常将共享资源操作放置在sunchronized定义的区域内,这样当其他线程也获取到这个锁时,必须等待锁被释放的时才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,0和1。一个线程运行到同步块时首先检查该对象的标志位,如果为0,表示次同步块中存在其他线程在运行,这是该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这是该对象的标志位被设置为1.该线程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。
同步方法-----同步方法就是在方法前面修饰synchronized关键字的方法:
synchronized void f(){
}
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错。
public synchronized void doit(){
if(num > 0){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("Tickets " + --num);
}
}
public void run(){
while (true){
doit();
}
}
将共享资源的操作放置在同步方法中,运行结果与使用同步块的结果一致。