Java多執行緒中join、yield、sleep方法詳解
- 2019 年 11 月 10 日
- 筆記
在Java多執行緒編程中,Thread類是其中一個核心和關鍵的角色。因此,對該類中一些基礎常用方法的理解和熟練使用是開發多執行緒程式碼的基礎。本篇主要總結一下Thread中常用的一些靜態方法的含義及程式碼中的使用。
sleep方法
源碼如下:
/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does not lose ownership of any monitors. * * @param millis * the length of time to sleep in milliseconds * * @throws IllegalArgumentException * if the value of {@code millis} is negative * * @throws InterruptedException * if any thread has interrupted the current thread. The * <i>interrupted status</i> of the current thread is * cleared when this exception is thrown. */ public static native void sleep(long millis) throws InterruptedException;
可以看到sleep是一個靜態的本地方法,因為是本地方法,所以並沒有java程式碼的實現,其實是調用了底層的C庫函數來實現的睡眠。
有一個long類型的參數,表示睡眠多少毫秒。
閱讀注釋,sleep方法的含義就是,讓當前正在執行任務的執行緒睡眠(臨時地停止執行)指定的毫秒數,這個精度和準確性是用系統時鐘和調度器保證的。但是,執行緒並不會釋放它擁有的鎖。
注意該方法會拋出InterruptedException中斷異常。
sleep不會釋放鎖程式碼示例:
public class ThreadsleepDemo{ private Object object = new Object(); public static void main(String[] args) { ThreadsleepDemo threadsleepDemo = new ThreadsleepDemo(); Thread thread1 = threadsleepDemo.new SleepDemoThread(); thread1.setName("執行緒1"); Thread thread2 = threadsleepDemo.new SleepDemoThread(); thread2.setName("執行緒2"); thread1.start(); thread2.start(); } class SleepDemoThread extends Thread{ @Override public void run() { synchronized (object){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } }
輸出結果如下:
執行緒1開始運行 執行緒1運行結束 執行緒2開始運行 執行緒2運行結束
可以多運行幾次,可能會有執行緒1在上面或和執行緒2在上面,但始終都是一個行程運行完了才會運行另一個執行緒,中間不會插入進來一個執行緒運行。
yield方法
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
當前執行緒對調度器的一個暗示,表示願意讓出CPU執行器的當前使用權,但是調度器可以自由忽略這個提示。
Yeild是一種在可能會過度使用一個CPU的多個執行緒之間提升相對進度試探性嘗試。它的使用應該結合詳細的性能分析和基準測試來進行,確保它確實有預期的效果。
很少使用這種方法。 它可能對調試或測試有用,可能有助於根據競態條件重現錯誤。 在設計並發控制結構(例如java.util.concurrent.locks包中的並行控制結構)時也可能有用。
join方法
join有三個重載的方法
join() join(long millis) join(long millis,int nanoseconds)
主要看下第二個方法的源碼
/** * Waits at most {@code millis} milliseconds for this thread to * die. A timeout of {@code 0} means to wait forever. * * <p> This implementation uses a loop of {@code this.wait} calls * conditioned on {@code this.isAlive}. As a thread terminates the * {@code this.notifyAll} method is invoked. It is recommended that * applications not use {@code wait}, {@code notify}, or * {@code notifyAll} on {@code Thread} instances. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
就是等待一個執行緒指定毫秒數後再消亡。無參數的join方法其實就是調用了join(0),即永遠等待下去。不過通過源碼我們可以看到,在while循環中有一個條件判斷,即isAlive()方法,意思是如果當前執行緒還活著,就會一直等待下去。
有點懵,看個例子應該加深下理解。比如睡前想刷個抖音。
刷抖音的工作我們交給一個執行緒來完成。
public class ScanDouyin extends Thread{ // 瀏覽抖音的時長 private int scanTime; public ScanDouyin(String name, int scanTime){ super(name); scanTime = this.scanTime; } @Override public void run() { System.out.println(getName() + ":開始刷抖音了"); try { // 刷抖音的時間 sleep(scanTime); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() +":抖音刷完了,睡覺吧"); } }
下面是準備睡覺的執行緒
/** * 準備睡覺了,睡前想要刷個抖音 */ public class ReadySleep extends Thread{ private ScanDouyin scanDouyin; public ReadySleep(String name,ScanDouyin scanDouyin){ super(name); this.scanDouyin = scanDouyin; } @Override public void run() { System.out.println(getName() + ":準備開始睡覺啦"); try { // 睡前刷把抖音 scanDouyin.join(); // 準備睡覺的具體內容 System.out.println("開始睡覺"); sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + ":zzzzzzzz,已經睡著了"); } public static void main(String[] args) { ScanDouyin scanDouyin = new ScanDouyin("刷抖音執行緒",10000); ReadySleep readySleep = new ReadySleep("睡覺執行緒",scanDouyin); readySleep.start(); scanDouyin.start(); } }
輸出結果如下:
睡覺執行緒:準備開始睡覺啦 刷抖音執行緒:開始刷抖音了 刷抖音執行緒:抖音刷完了,睡覺吧 開始睡覺 睡覺執行緒:zzzzzzzz,已經睡著了
這裡我們我設置的刷抖音的時間是10s,睡覺執行緒的執行時間是100ms,也就是0.1s。
可以看到因為在睡覺執行緒中調用了刷抖音執行緒的join方法,使得睡覺的執行緒必須等待直到刷完抖音(刷抖音執行緒執行完畢,執行緒消亡),才能開始睡覺。
至此,應該可以明白,如果某個執行緒在另一個執行緒t上調用t.join(),此執行緒將被掛起,直到目標執行緒t結束才恢復(即t.isAlive()方法返回假)。