在Java最早期的时候,常常采用suspend()和resume()方法对线程进行阻塞和唤醒,但是现在不再推荐使用了,是因为:
suspend()方法在导致线程阻塞的过程中,不会释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行resume()方法后,被挂起的线程才能继续,从而其他被阻塞在这个锁的线程才能继续执行。
如果resume操作出现在suspend操作之前,那么线程就会一直处于阻塞状态–>产生死锁,但是对于被挂起的线程,他的线程仍旧是Runnable状态
实例
class SuspendResumeTest {
public static Object object = new Object();
public static class TestThread extends Thread {
public TestThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (object) {
System.out.println(getName() + "占用。。。");
Thread.currentThread().suspend();
}
}
}
static TestThread t1 = new TestThread("我是线程1");
static TestThread t2 = new TestThread("我是线程2");
public static void main(String[] args) throws InterruptedException {
t1.start();
//Thread.sleep(200);
t2.start();
t1.resume();
t2.resume();
t1.join();
t2.join();
}
}
执行结果:
产生死锁!!!
接下来让我们来说一下,常见的,推荐的wait和notify方法进行阻塞和唤醒吧~
在Object类中,定义了wait(),notify()和notifyAll()等接口。wait方法的作用是让当前线程进入等待状态,同时wait也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有线程。
- notify:唤醒在此对象监视器上等待的单个线程
- notifyAll:唤醒在此对象监视器上等待的所有线程
- wait:让当前线程处于“阻塞”状态,直到其他线程调用该对象的notify方法或者notifyAll方法,当前线程被唤醒
- wait(long timeout):让当前线程处于“阻塞”状态,直到其他线程调用该对象的notify方法或者notifyAll方法,或者超过指定的时间,当前线程被唤醒
- wait(long timeout, int nanos):让当前线程处于“阻塞”状态,直到其他线程调用该对象的notify方法或者notifyAll方法,或者其他某个线程中断当前线程,或者已经超过某个实际时间量,当前线程被唤醒
实例
public class WaitTest {
public static void main(String[] args) {
ThreadA t1=new ThreadA("t1");
synchronized (t1){
try {
//启动线程1
System.out.println(Thread.currentThread().getName()+"start t1");
t1.start();
//主线程等待线程1通过notify唤醒
System.out.println(Thread.currentThread().getName()+"wait()");
t1.wait();
System.out.println(Thread.currentThread().getName()+"continue");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
@Override
public void run() {
synchronized (this){
System.out.println(Thread.currentThread().getName()+"call notify()");
//唤醒当前的wait线程
notify();
}
}
}
######执行结果
结果说明
- 图中的主线程代表main线程,线程t1代表waitTest中启动的线程1,而锁代表t1这个对象的同步锁
- 主线程通过new ThreadA(“t1”)新建线程“t1”,随后通过synchronized(t1)获取t1对象的同步锁,然后调用t1.start()启动线程t1
- 主线程执行t1.wait()释放"t1对象的锁"并且进入阻塞状态,等待t1对象上的线程通过notify或者notifyAll将其唤醒
- 线程t1运行之后,通过synchronized(this)获取当前对象的锁,接着调用notify唤醒当前对象上等待 的线程,也就是主线程
- 线程t1运行完毕之后,释放当前对象的锁,紧接着,主线程获取t1对象的锁,然后接着运行
####扩展一下wait()–>wait(long timeout)
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " run ");
// 死循环,不断运行。
while(true)
;
}
}
public class WaitTimeoutTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start();
// 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
System.out.println(Thread.currentThread().getName() + " call wait ");
t1.wait(3000);
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果
main start t1
main call wait
t1 run // 大约3秒之后...输出“main continue”
main continue
结果说明
和上面的wait方法类似,只不过主线程不会立刻被唤醒,而是等待3000ms后唤醒
扩展一下notify–>notifyAll
public class NotifyAllTest {
private static Object obj = new Object();
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
ThreadA t3 = new ThreadA("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
// 主线程等待唤醒。
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run() {
synchronized (obj) {
try {
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " wait");
// 唤醒当前的wait线程
obj.wait();
// 打印输出结果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
结果说明
- 主线程中新建并启动了3个线程"t1" “t2” “t3”
- 主线程通过sleep(3000)休眠3秒,在主线程休眠3秒的过程中,我们假设"t1" “t2” “t3” 这3个线程都运行了,以"t1"为例,当它运行的时候,它会执行obj.wait()等待其他线程通过notify或者notifyAll方法来唤醒她,同样的道理,t2和t3也会等待其他线程通过notify或者notifyAll来唤醒它们
- 主线程休眠3秒后,接着运行,执行obj.notifyAll()唤醒obj上的等待线程,即唤醒"t1" “t2” "t3"这3个线程,紧接着,主线程的synchronized(obj)运行完毕后,主动释放obj锁,这样t1 t2 t3就可以获取锁资源接着运行了。
notify() wait()等函数定义在object中,而不是Thread中的原因
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
LockSupport提供的park和unpark方法
这两个方法是基于suspend和resume这一组合的基础上演变过来的,提供避免死锁和竞态条件
public class ThreadParkTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setName("mt");
mt.start();
try {
Thread.currentThread().sleep(10);
mt.park();
Thread.currentThread().sleep(30000);
mt.unPark();
Thread.currentThread().sleep(30000);
mt.park();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyThread extends Thread {
private boolean isPark = false;
public void run() {
System.out.println(" Enter Thread running.....");
while (true) {
if (isPark) {
System.out.println("Thread is Park.....");
LockSupport.park();
}
}
}
public void park() {
isPark = true;
}
public void unPark() {
isPark = false;
LockSupport.unpark(this);
System.out.println("Thread is unpark.....");
}
}
}
为什么使用park和unpark方法可以避免死锁的产生?
park和unpark方法控制的粒度更细,能够准确的决定线程在某个点停止
引入了许可机制,逻辑为:
1、park讲许可在等于0的时候阻塞,等于1的时候返回并将许可减为0
2、unpark尝试唤醒线程,许可加1。根据这两个逻辑,对于同一条线程,park与unpark先后操作的顺序似乎并不影响程序正确地执行,假如先执行unpark操作,许可则为1,之后再执行park操作,此时因为许可等于1直接返回往下执行,并不执行阻塞操作。
总结总结总结
suspend()、resume()已经被deprecated,不建议使用。wait与notify要保证必须有锁才能执行,而且执行notify操作释放锁后还要将当前线程扔进该对象锁的等待队列。LockSupport则完全不用考虑对象、锁、等待队列,真正解耦合线程之间的同步。