八種控制執行緒順序的方法
各位看官,我去年路過陳家村時,聽到大神們在討論一些排序演算法,比如猴子排序法、睡眠排序法等,猴子排序法就是給猴子一堆亂序的數,
讓它自己玩,最後總有一個順序是對的!睡眠排序法,按數的大小分配執行緒睡眠時間,數越大睡眠時間就越長,然後同時啟動全部執行緒,按
先後輸出排序即成!想想也不無道理,那我就展開說說睡眠排序法,如何玩轉執行緒執行順序控制。
準備:
Idea2019.03/Gradle6.0.1/JDK11.0.4
難度: 新手--戰士--老兵--大師
目標:
- 實現八種控制執行緒順序的方法
步驟:
為了遇見各種問題,同時保持時效性,我儘量使用最新的軟體版本。程式碼地址:本次無
第一招:執行緒配合join
publicclass ThreadJoinDemo { public static void main(String[] args) { final Thread thread1 = new Thread( ()->{ System.out.println("先買菜"); } ); final Thread thread2 = new Thread( ()->{ try { thread1.join(); //Waits for this thread to die. } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("再煎蛋"); } ); final Thread thread3 = new Thread( ()->{ try { thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("後吃飯"); } ); thread3.start(); thread1.start(); thread2.start(); } }
解析:呼叫thread.join()方法,當前執行緒將等待被join執行緒執行結束
第二招:主執行緒配合join
publicclass ThreadJoinDemo2 { public static void main(String[] args) throws InterruptedException { final Thread thread1 = new Thread( ()->{ System.out.println("先買菜"); } ); final Thread thread2 = new Thread( ()->{ System.out.println("再煎蛋"); } ); final Thread thread3 = new Thread( ()->{ System.out.println("後吃飯"); } ); thread1.start(); thread1.join(); thread2.start(); thread2.join(); thread3.start(); } }
解析:同上
第三招:synchronized鎖,配合鎖的wait/siganl喚醒機制
publicclass ThreadJoinDemo3 { privatestaticfinalbyte[] myLock1 = newbyte[0]; privatestaticfinalbyte[] myLock2 = newbyte[0]; privatestatic Boolean t1Run = false; privatestatic Boolean t2Run = false; public static void main(String[] args) { final Thread thread1 = new Thread( ()->{ synchronized (myLock1){ System.out.println("先買車"); t1Run = true; myLock1.notifyAll(); } } ); final Thread thread2 = new Thread( ()->{ synchronized (myLock1){ try { if (!t1Run){ System.out.println("買菜路上。。。"); myLock1.wait(); } synchronized (myLock2){ t2Run = true; System.out.println("後吃飯"); myLock2.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } ); final Thread thread3 = new Thread( ()->{ synchronized (myLock2){ try { if (!t2Run){ System.out.println("煎蛋糊了。。。"); myLock2.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("後吃飯"); myLock2.notifyAll(); } } ); thread3.start(); thread2.start(); thread1.start(); } }
解析:執行執行緒前,先去嘗試獲取鎖,如果獲取失敗,就進入等待狀態,
注意 1.加了兩個狀態變數的作用:如果thread1先執行完了,thread2才執行,thread2在等待thread1喚醒,這將導致thread2永遠等待,
因為wait將使得當前執行緒進入等待直到被喚醒,2.使用空的byte[]陣列做鎖物件,為啥?因為體積小,效率高啊!
第四招:newSingleThreadExecutor執行緒池
publicclass ThreadJoinDemo4 { public static void main(String[] args) { final Thread thread1 = new Thread( ()->{ System.out.println("先買菜"); } ); final Thread thread2 = new Thread( ()->{ System.out.println("再煎蛋"); } ); final Thread thread3 = new Thread( ()->{ System.out.println("後吃飯"); } ); ExecutorService threadPool = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory()); threadPool.submit(thread1); threadPool.submit(thread2); threadPool.submit(thread3); // 關閉執行緒池:生產環境請註釋掉,請君思考為啥? threadPool.shutdown(); } }
解析:newSingleThreadExecutor執行緒池物件中只有一個執行緒來執行任務,就會按照接收的任務順序執行,只需按序提交任務即可。
第五招:lock配合condition
publicclass ThreadJoinDemo5 { privatestaticfinal Lock lock = new ReentrantLock(); privatestaticfinal Condition condition1 = lock.newCondition(); privatestaticfinal Condition condition2 = lock.newCondition(); privatestatic Boolean t1Run = false; privatestatic Boolean t2Run = false; public static void main(String[] args) { final Thread thread1 = new Thread( ()->{ // 注意lock/tryLock的區別: lock是void,沒獲取到鎖,則進入休眠,tryLock是返回Boolean,執行後立即返回true/false lock.lock(); System.out.println("先買菜"); condition1.signal(); t1Run = true; // 生產環境下這裡最好使用try/finally確保unlock執行 lock.unlock(); } ); final Thread thread2 = new Thread( ()->{ lock.lock(); try { if (!t1Run){ // Causes the current thread to wait until it is signalled condition1.await(); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("再煎蛋"); t2Run = true; condition2.signal(); lock.unlock(); } ); final Thread thread3 = new Thread( ()->{ lock.lock(); try { if (!t2Run){ condition2.await(); } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("後吃飯"); lock.unlock(); } ); thread3.start(); thread2.start(); thread1.start(); } }
解析:lock物件上建立兩個condition,執行緒執行前先加鎖,若不是預期順序的執行緒啟動,則在該condition上進行wait等待直到收到signal訊號,
注意點:condition 和 lock 執行先後關係,Before waiting on the condition the lock must be held by the current thread. await() will atomically
release the lock before waiting and re-acquire the lock before the wait returns.
第六招:CountDownLatch
publicclass ThreadJoinDemo6 { privatestatic CountDownLatch countDownLatch1 = new CountDownLatch(1); privatestatic CountDownLatch countDownLatch2 = new CountDownLatch(1); public static void main(String[] args) { final Thread thread1 = new Thread( ()->{ countDownLatch1.countDown(); System.out.println("先買菜"); } ); final Thread thread2 = new Thread( ()->{ try { // 注意這裡不要寫成 Object.wait() countDownLatch1.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("再煎蛋"); countDownLatch2.countDown(); } ); final Thread thread3 = new Thread( ()->{ try { countDownLatch2.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("後吃飯"); } ); thread3.start(); thread1.start(); thread2.start(); } }
解析:CountDownLatch即“倒計數”,只有計數變為零時,參與者才能執行,我們設定兩個倒計數器,都置為1,非預期順序的執行緒,必須等待計數歸零。
第七招:Semaphore訊號量法
publicclass ThreadJoinDemo7 { privatestatic Semaphore semaphore1 = new Semaphore(0); privatestatic Semaphore semaphore2 = new Semaphore(0); public static void main(String[] args) { final Thread thread1 = new Thread( ()->{ semaphore1.release(); System.out.println("先買菜"); } ); final Thread thread2 = new Thread( ()->{ try { semaphore1.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("再煎蛋"); semaphore2.release(); } ); final Thread thread3 = new Thread( ()->{ try { semaphore2.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("後吃飯"); } ); thread1.start(); thread3.start(); thread2.start(); } }
解析:Semaphore訊號量物件,可以存放N個訊號量,可以原子性的釋放和請求這N個訊號量,我們先預設兩個存放0個訊號量的物件,
非預期順序的執行緒啟動後,無法獲取到訊號量,進入等待,直到前序執行緒釋放訊號量。
注意:Semaphore中可以為負值,這時候,就必須確保release發生在acquire前面,比如Semaphore(0)和Semaphore(-1)的情況:
Semaphore(-1)的release可以,require則進入休眠,Semaphore(0)的release可以,require則進入休眠,即只有permit大於0時,才能require成功!
第八招:終極大法,睡眠法!
略,留個家庭作業!
後記:
- lambda表示式,如果看官還覺得我這個執行緒建立的寫法不太爽,那就落伍啦,別再用下面的寫法了,如果使用Idea,則會自動提示轉為lambda表示式:
-
Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("寫成這樣,表示您落伍了!"); } });
- CyclicBarrier(迴環柵欄),我想了下,這個物件不適合控制順序,只適合執行緒相互等待,然後一起執行,比如我們約好今天一起去吃大餐,集合後,至於誰先邁出出發的第一步,這個沒法控制,故舍棄不用,
全文完!
我的其他文章:
- 1 移動應用APP購物車(店鋪系列二)
- 2 H5開發移動應用APP(店鋪系列一)
- 3 阿里雲平臺OSS物件儲存
- 4 Dubbo學習系列之十七(微服務Soul閘道器)
- 5 Docker部署RocketMQ
只寫原創,敬請關注