方式1:早期JAVA采用suspend()、resume()对线程进行阻塞与唤醒,但这种方式产生死锁的风险很大,因为线程被挂起以后不会释放锁,可能与其他线程、主线程产生死锁,如:
public class ThreadSuspendTest { public static void main(String[] args) { Thread mt = new MyThread(); mt.start(); try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } mt.suspend(); System.out.println("suspend complete?"); mt.resume(); } static class MyThread extends Thread { public void run() { while (true) { System.out.println("running...."); } } }}
方式2:wait、notify形式通过一个object作为信号,object的wait()方法是锁门的动作,notify()、notifyAll()是开门的动作,某一线程一旦关上门后其他线程都将阻塞,直到别的线程打开门。notify()准许阻塞的一个线程通过,notifyAll()允许所有线程通过。如下例子:主线程分别启动两个线程,随后通知子线程暂停等待,再逐个唤醒后线程抛异常退出。
public class ObjectWaitTest { public static Object waitObject = new Object(); public static void notifyAllThread() { System.out.println("notifyAllThread"); synchronized (waitObject) { waitObject.notifyAll(); } } public static void notifyThread() { System.out.println("notifyThread"); synchronized (waitObject) { waitObject.notify(); } } public static void main(String[] args) { MyThread tm1 = new MyThread(waitObject); tm1.setName("tm1"); tm1.start(); MyThread tm2 = new MyThread(waitObject); tm2.setName("tm2"); tm2.start(); try { Thread.currentThread().sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } tm1.suspendThread(); tm2.suspendThread(); try { Thread.currentThread().sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } notifyThread(); try { Thread.currentThread().sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } notifyThread(); } static class MyThread extends Thread { public Object waitObject = null; private boolean isStop = false; public MyThread(Object waitObject) { this.waitObject = waitObject; } public void run() { while (true) { synchronized (waitObject) { if (isStop) { System.out.println(Thread.currentThread().getId() + " is stop"); try { waitObject.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + " is resume"); System.out.println(Thread.currentThread().getId() + " will exit"); throw new RuntimeException(Thread.currentThread().getId() +" exit"); } } } } public void suspendThread() { this.isStop = true; } }}
wait、notify使用要点:
1、对象操作都需要加同步synchronized;
2、线程需要阻塞的地方调用对象的wait方法;
存在的不足:面向对象的阻塞是阻塞当前线程,而唤醒的是随机的一个线程或者所有线程,偏重线程间的通信;同时某一线程在被另一线程notify之前必须要保证此线程已经执行到wait等待点,错过notify则可能永远都在等待。
方式3: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引入了许可机制,许可逻辑为:
①park将许可在等于0的时候阻塞,等于1的时候返回并将许可减为0;
②unpark尝试唤醒线程,许可加1。根据这两个逻辑,对于同一条线程,park与unpark先后操作的顺序似乎并不影响程序正确地执行,假如先执行unpark操作,许可则为1,之后再执行park操作,此时因为许可等于1直接返回往下执行,并不执行阻塞操作。
park与unpark组合真正解耦了线程之间的同步,不再需要另外的对象变量存储状态,并且也不需要考虑同步锁,wait与notify要保证必须有锁才能执行,而且执行notify操作释放锁后还要将当前线程扔进该对象锁的等待队列,LockSupport则完全不用考虑对象、锁、等待队列等问题。
总结:suspend()、resume()已经被deprecated,不建议使用。wait、notify需要对对象加同步,性能有折扣。LockSupport则完全不用考虑对象、锁、等待队列。