线程中断、线程让步、线程睡眠、线程合并的使用推荐翻阅这篇博客:http://blog.csdn.net/ghsau/article/details/17560467
本文主要补充wait(),notify()和sleep()区别与联系以及为何不推荐使用stop(),suspend()。
1. wait(),notify()
wait(),notify()和notifyall()方法是java.lang.Object类为线程提供的用于实现线程间通信的同步控制方法。
线程等待有两种调用格式:
1.wait()等待通信线程唤醒后再继续执行本线程。
2.wait(long millis)等待通信线程唤醒或者最多等待millis毫秒后,再继续执行本线程。
调用wait()和notify()系列方法时,当前线程必须拥有此对象监视器(即对象锁)。如果当前线程不是此对象监视器的所有者,会抛IllegalMonitorStateException。
通过以下三种方法之一,线程可以成为对象监视器的所有者:
* 通过执行此对象的同步实例方法。
* 通过执行在此对象上进行同步的 synchronized 语句的正文。
* 对于 Class 类型的对象,可以通过执行该类的同步静态方法。
注意1:对于一个对象,某一时刻只能有一个线程拥有该对象的监视器。
package com.blog.spring.thread;
public class WaitTest {
public static void main(String[] args) {
WaitTest waitTest = new WaitTest();
waitTest.startThread();
}
/**
* 线程锁
*/
private final Object object = new Object();
/**
* 启动线程
*/
public void startThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("开始执行线程。。。");
System.out.println("进入等待状态。。。");
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束。。。");
}
});
t.start();
}
}
上面的锁也可以用this代表当前对象,表示当前类的对象锁。
运行结果
开始执行线程。。。
进入等待状态。。。
程序在未被唤醒之后,将不再打印“线程结束”,并且程序无法执行完毕一直处于等待状态。当前线程暂停执行,线程进入Blocked状态,cpu不会分给其时间,等待其他线程执行notify()方法或者notifyall()方法。也可以用interrupt()取消.注意:线程调用interrupt(),则线程会取消等待状态,其中断状态将被清除,它还将收到一个 InterruptedException。 我们可以捕获该异常,并且做一些处理详细参考《线程中断》。
它还有种调用格式:wait(long millis)等待通信线程唤醒或者最多等待millis毫秒后,再继续执行本线程。
注意:notify()方法或者notifyall()方法是用来唤醒其它线程的,线程处于等待后,不能执行任何方法,只能由其它线程唤醒或者当前线程被中断。
中断示例代码如下:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread("MyThread");
t.start();
//线程中断
t.interrupt();
}
static class MyThread extends Thread {
int i = 0;
public MyThread(String name) {
super(name);
}
public void run() {
synchronized (this){
System.out.println("线程开始等待");
try {
this.wait();
System.out.println("线程已经被中断,该信息无法被打印");
} catch (InterruptedException e) {
System.out.println("捕获到中断异常");
}
System.out.println("线程等待状态被取消");
if(isInterrupted()){//如果线程被标记为中断状态
System.out.println("线程被中断");
}
}
}
}
}
不调用中断方法运行结果:
线程开始等待
调用线程中断方法运行结果:
线程开始等待
捕获到中断异常
线程等待状态被取消
Process finished with exit code 0
当一个线程处于wait()状态时,也即等待它之前所持有的object’s monitor被释放,通过notify()方法可以让该线程重新处于活动状态,从而去抢夺object’s monitor,唤醒该线程。如果多个线程同时处于等待状态,那么调用notify()方法只能随机唤醒一个线程。在同一时间内,只有一个线程能够获得object’s monitor,执行完毕之后,则再将其释放供其它线程抢占。
那么顾名思义,notifyAll()就是用来唤醒正在等待状态中的所有线程的,不过也需要注意以下几点:
(1)notifyAll()只会唤醒那些等待抢占指定object’s monitor的线程,其他线程则不会被唤醒。
(2)notifyAll()只会一个一个的唤醒,而并非统一唤醒。因为在同一时间内,只有一个线程能够持有object’s monitor
(3)notifyAll()只是随机的唤醒线程,并非有序唤醒。
思考:怎么做到有序唤醒,大家可以思考下怎么实现?
2. wait(),sleep()的区别
sleep()方法来自于Thread类,从源码给出的解释来看,sleep()方法可以做到如下几点:
(1)首先,调用sleep()之后,会引起当前执行的线程进入暂时中断状态,也即睡眠状态。
(2)其次,虽然当前线程进入了睡眠状态,但是依然持有monitor对象。
(3)在中断完成之后,自动进入唤醒状态从而继续执行代码。
那么从以上的理论和实践来分析,我们能得出如下结论:
(1)在线程的运行过程中,调用该线程持有monitor对象的wait()方法时,该线程首先会进入等待状态,并将自己持有的monitor对象释放。
(2)如果一个线程正处于等待状态时,那么唤醒它的办法就是开启一个新的线程,通过notify()或者notifyAll()的方式去唤醒或者等待时间结束或者中断。当然,需要注意的一点就是,唤醒方法调用必须是同一个monitor对象。
(3)sleep()方法虽然会使线程中断,但是不会将自己的monitor对象释放,在中断结束后,依然能够保持代码继续执行。
3. stop(),suspend()不好的原因
stop()方法作为一种粗暴的线程终止行为,在线程终止之前没有对其做任何的清除操作,因此具有固有的不安全性。 用Thread.stop()方法来终止线程将会释放该线程对象已经锁定的所有监视器。如果以前受这些监视器保护的任何对象都处于不连贯状态,那么损坏的对象对其他线程可见,这有可能导致不安全的操作。 由于上述原因,因此不应该使用stop()方法,而应该在自己的Thread类中置入一个标志,用于控制目标线程是活动还是停止。如果该标志指示它要停止运行,可使其结束run()方法。如果目标线程等待很长时间,则应使用interrupt()方法来中断该等待。
suspend()方法 该方法已经遭到反对,因为它具有固有的死锁倾向。调用suspend()方法的时候,目标线程会停下来。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前,其他线程都不能访问该资源。除非被挂起的线程恢复运行。对任何其他线程来说,如果想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。由于上述原因,因此不应该使用suspend()方法,而应在自己的thread类中置入一个标志,用于控制线程是活动还是挂起。如果标志指出线程应该挂起,那么用wait()方法命令其进入等待状态。如果标志指出线程应当恢复,那么用notify()方法重新启动线程。
对线程合并的补充解释:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
JoinThread t1 = new JoinThread("t1");
JoinThread t2 = new JoinThread("t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("主线程开始执行!");
}
}
class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
}
public void run() {
for(int i = 1; i <= 10; i++)
System.out.println(getName()+":"+getId() + "执行了" + i + "次");
}
}
调用t1.join();t2.join();程序的运行结果:
t1:11执行了1次
t2:12执行了1次
t1:11执行了2次
t2:12执行了2次
t1:11执行了3次
t2:12执行了3次
t1:11执行了4次
t1:11执行了5次
t2:12执行了4次
t1:11执行了6次
t2:12执行了5次
t2:12执行了6次
t2:12执行了7次
t1:11执行了7次
t2:12执行了8次
t1:11执行了8次
t2:12执行了9次
t2:12执行了10次
t1:11执行了9次
t1:11执行了10次
主线程开始执行!
不调用t1.join();t2.join();程序的运行结果:
主线程开始执行!
t1:11执行了1次
t2:12执行了1次
t1:11执行了2次
t2:12执行了2次
t2:12执行了3次
t1:11执行了3次
t1:11执行了4次
t1:11执行了5次
t1:11执行了6次
t1:11执行了7次
t2:12执行了4次
t2:12执行了5次
t2:12执行了6次
t2:12执行了7次
t2:12执行了8次
t2:12执行了9次
t2:12执行了10次
t1:11执行了8次
t1:11执行了9次
t1:11执行了10次
相信以上运行结果能够帮助大家更明白线程合并!
线程通信还有更高级有效的方式:Condition,我放在 多线程(五)线程同步(中)-Lock,Condition, ReadWriteLock
欢迎大家去踩踩!
参考文章:
(http://blog.csdn.net/kaka534/article/details/51849285)
(http://blog.csdn.net/ghsau/article/details/17560467)
http://blog.csdn.net/hudashi/article/details/7001070
http://blog.sina.com.cn/s/blog_687c844d0101ietn.html