在多線程學習的過程中涉及的方法和接口特別多,本文就詳細講解下經常使用方法的作用和使用場景。
1.sleep()方法。
當線程對象調用sleep(time)方法后,當前線程會等待指定的時間(time),並讓出cpu執行權,但是它的監控狀態依然當前對象的保持者(不會釋放對象鎖),當指定的時間到了又會自動恢復運行狀態。
2.wait()和notify()/notifyAll()方法。
wait()和notify()、notifyAll()方法的調用都必須在synchronized修飾的方法或者代碼塊中調用,使用過程中必須獲得對象鎖,否則會拋出java.lang.IllegalMonitorStateException的異常。
執行wait()方法,會使當前線程的狀態變為阻塞狀態並交出對象鎖。
執行notify()方法,會隨機挑選一個當前對象阻塞隊列中的線程並將狀態變為就緒狀態。
執行notifyAll()方法,會將當前對象阻塞隊列中的所有線程的狀態變為就緒狀態。
wait()和notify()、notifyAll()為什么都是Object類中的方法。因為synchronized中的這把鎖可以是任意對象,所以任意對象都可以調用wait()和notify();所以wait和notify屬於Object。
專業說:因為這些方法在操作同步線程時,都必須要標識它們操作線程的鎖,只有同一個鎖上的被等待線程,可以被同一個鎖上的notify喚醒,不可以對不同鎖中的線程進行喚醒。
也就是說,等待和喚醒必須是同一個鎖。而鎖可以是任意對象,所以可以被任意對象調用的方法是定義在object類中。
3.join()方法。
在A線程中調用了B線程的join()方法時,表示只有當B線程執行完畢時,A線程才能繼續執行。並且join()方法是會釋放鎖的,因為底層使用 wait()
方法來實現的。
4.yield()方法。
yield()讓當前正在運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行的機會。(不會釋放鎖)
因此,使用yield()的目的是讓具有相同優先級的線程之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因為,讓步的線程可能被線程調度程序再次選中。
4.interrupt()和isInterrupted()/interrupted()方法。
thread.interrupt(),當thread對象變為中斷狀態,interrupt()並不能中斷在運行中的線程,它只能改變中斷狀態而已。
thread.interrupted(),判斷當前線程對象的狀態是否為中斷狀態,內部實現是調用的當前線程的isInterrupted(),並且會重置當前線程的中斷狀態。
thread.isInterrupted(),判斷當前線程對象的狀態是否為中斷狀態,不會重置當前線程的中斷狀態。
5.CountDownLatch類。
CountDownLatch是一個同步工具類,它允許一個或多個線程一直等待,直到其他線程的操作執行完后再執行。
1 CountDownLatch countDownLatch=new CountDownLatch(5);//CountDownLatch初始化等待5個線程,等待5個線程執行完主線程再執行。 2 3 countDownLatch.await();//主線程阻塞 4 5 countDownLatch.await(1000, TimeUnit.SECONDS);//主線程阻塞,超時后count還沒有為0的話,主線程執行。 6 7 countDownLatch.countDown();//將count值減1,當count值為0后,主線程執行。
使用場景:
①某一線程在開始運行前等待n個線程執行完畢。將 CountDownLatch 的計數器初始化為n :new CountDownLatch(n) ,每當一個任務線程執行完畢,就將計數器減1 countdownlatch.countDown(),當計數器的值變為0時,在CountDownLatch上 await()的線程就會被喚醒。一個典型應用場景就是啟動一個服務時,主線程需要等待多個組件加載完畢,之后再繼續執行。
②實現多個線程開始執行任務的最大並行性。注意是並行性,不是並發,強調的是多個線程在某一時刻同時開始執行。類似於賽跑,將多個線程放到起點,等待發令槍響,然后同時開跑。做法是初始化一個共享的 CountDownLatch 對象,將其計數器初始化為 1 :new CountDownLatch(1) ,多個線程在開始執行任務前首先 coundownlatch.await(),當主線程調用 countDown() 時,計數器變為0,多個線程同時被喚醒。
③死鎖檢測:一個非常方便的使用場景是,你可以使用n個線程訪問共享資源,在每次測試階段的線程數目是不同的,並嘗試產生死鎖。
6.LockSupport類。
LockSupport可以起到和wait()一樣的作用。在沒有LockSupport之前,線程的掛起和喚醒咱們都是通過Object的wait和notify/notifyAll方法實現。
public class TestObjWait { public static void main(String[] args)throws Exception { Thread A = new Thread(new Runnable() { @Override public void run() { int sum = 0; for(int i=0;i<10;i++){ sum+=i; } LockSupport.park();//阻塞線程 System.out.println(sum); } }); A.start(); //睡眠一秒鍾,保證線程A已經計算完成,阻塞在wait方法 Thread.sleep(1000); LockSupport.unpark(A);//喚醒阻塞線程 } }
總結一下,LockSupport比Object的wait/notify有兩大優勢:
①LockSupport不需要在同步代碼塊里 。所以線程間也不需要維護一個共享的同步對象了,實現了線程間的解耦。
②unpark函數可以先於park調用,所以不需要擔心線程間的執行的先后順序。