方式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則完全不用考慮對象、鎖、等待隊列。
