天天看點

java 線程等待隊列_Java多線程學習(五)——等待通知機制

等待通知機制的實作

方法wait()的作用是使目前線程進行等待,wait()方法是Object類的方法,該方法用來将目前線程放到“預執行隊列”,并在wait()所在的代碼處停止執行,直到接到通知或中斷為止。隻能在同步方法或同步快中使用wait()方法,執行wait()後,目前線程釋放鎖。

方法notify()也要在同步方法或同步快中調用,在調用前也必須獲得該對象的的對象級别鎖。該方法用來通知那些可能等待該對象的對象鎖的其他線程,如果有多個線程等待,則由線程規劃器随機選出一個wait狀态的線程,對其發出notify通知,使他等待擷取對象鎖。

在執行notify()後目前線程不會馬上釋放鎖,會線上程退出synchronized代碼塊才會釋放鎖,呈wait狀态的線程才可以擷取鎖。當第一個擷取對象鎖的wait線程運作結束釋放鎖後,如果該對象沒有再次notify,其他wait狀态的線程依然會阻塞wait狀态,直到這個對象發出notify或notifyAll。

public class MyWait {

private final Object lock;

MyWait(Object lock){

this.lock=lock;

}

public void waitTest(){

try {

synchronized (lock){

System.out.println("開始 wait time = " + System.currentTimeMillis());

lock.wait();

System.out.println("結束 wait time = " + System.currentTimeMillis());

}

}catch (InterruptedException e){

e.printStackTrace();

}

}

}

public class MyNotify {

private final Object lock;

MyNotify(Object lock){

this.lock=lock;

}

public void notifyTest(){

synchronized (lock){

System.out.println("開始 notify time = " + System.currentTimeMillis());

lock.notify();

System.out.println("結束 notify time = " + System.currentTimeMillis());

}

}

}

public class Main {

public static void main(String[] args){

try {

Object lock = new Object();

MyWait myWait = new MyWait(lock);

new Thread(() -> myWait.waitTest()).start();

Thread.sleep(3000);

MyNotify myNotify = new MyNotify(lock);

new Thread(() -> myNotify.notifyTest()).start();

}catch (InterruptedException e){

e.printStackTrace();

}

}

}

開始 wait time = 1552812964325

開始 notify time = 1552812967328

結束 notify time = 1552812967328

結束 wait time = 1552812967328

從輸出内容可以看出3秒後執行notify方法,并在notify方法執行結束後才執行wait後的方法。

相關方法wait() :使調用該方法的線程釋放共享資源鎖,然後從運作狀态退出,進入等待隊列,直到被再次喚醒。

wait(long):逾時等待一段時間,這裡的參數時間是毫秒,也就是等待長達n毫秒,如果沒有通知就逾時傳回。

notify():随機喚醒等待隊列中等待同一共享資源的 “一個線程”,并使該線程退出等待隊列,進入可運作狀态,也就是notify()方法僅通知“一個線程”。

notifyAll():使所有正在等待隊列中等待同一共享資源的 “全部線程” 退出等待隊列,進入可運作狀态。此時,優先級最高的那個線程最先執行,但也有可能是随機執行,這取決于JVM虛拟機的實作。

線程的基本狀态

建立(new):新建立了一個線程對象。

可運作(runnable):線程對象建立後,其他線程(比如main線程)調用了該對象的start()方法。該狀态的線程位于可運作線程池中,等待被線程排程選中,獲 取cpu的使用權。

運作(running):可運作狀态(runnable)的線程獲得了cpu時間片(timeslice),執行程式代碼。

阻塞(block):阻塞狀态是指線程因為某種原因放棄了cpu使用權,也即讓出了cpu timeslice,暫時停止運作。直到線程進入可運作(runnable)狀态,才有 機會再次獲得cpu timeslice轉到運作(running)狀态。阻塞的情況分三種:

(一). 等待阻塞:運作(running)的線程執行o.wait()方法,JVM會把該線程放 入等待隊列(waitting queue)中。

(二). 同步阻塞:運作(running)的線程在擷取對象的同步鎖時,若該同步鎖 被别的線程占用,則JVM會把該線程放入鎖池(lock pool)中。

(三). **其他阻塞**: 運作(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀态。當sleep()狀态逾時join()等待線程終止或者逾時、或者I/O處理完畢時,線程重新轉入可運作(runnable)狀态。死亡(dead):線程run()、main()方法執行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次複生。

本節代碼lgsxiaosen/notes-code​github.com

java 線程等待隊列_Java多線程學習(五)——等待通知機制

方法join的使用

在很多情況,主線程建立并啟動子線程,如果子線程中進行大量的耗時運算,主線程往往将遭遇子線程結束之前結束。如果主線程要等待子線程執行完成之後在結束,就要使用join()方法,join()作用是等待線程對象銷毀。

Thread類除了提供join()方法之外,還提供了join(long millis)、join(long millis, int nanos)兩個具有逾時特性的方法。這兩個逾時方法表示,如果線程thread在指定的逾時時間沒有終止,那麼将會從該逾時方法中傳回。

public class Main {

public static void main(String[] args) throws InterruptedException{

Thread thread = new Thread(() -> {

try {

System.out.println(Thread.currentThread().getName()+"正在執行");

Thread.sleep(1000);

}catch (InterruptedException e){

e.printStackTrace();

}

}, "線程1");

thread.start();

thread.join();

System.out.println("等待"+thread.getName()+"執行完");

}

}

// 輸出線程1正在執行

等待線程1執行完

jain(long)與sleep(long)的差別

方法join(long)的功能是在内部使用wait(long)方法來實作的,是以join(long)方法具有釋放鎖的特點。二sleep(long)不會釋放鎖。

ThreadLocal的使用

變量值共享可以使用public static變量的形式,所有線程都使用同一個public static變量,如果想實作每個線程都有自己的共享變量可以使用ThreadLocal來解決。

ThreadLocal的相關方法:get():傳回目前線程的此線程局部變量的副本中的值。

set(T value): 将目前線程的此線程局部變量的副本設定為指定的值。

remove():删除此線程局部變量的目前線程的值。

initialValue(): 傳回此線程局部變量的目前線程的“初始值”。

線程變量間的隔離性

public class ThreadLocalTeat {

public static ThreadLocal threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException{

int count = 30;

String name = "Thread-";

for (int i=0; i

Thread thread = new Thread(() -> {

threadLocal.set(Thread.currentThread().getName());

System.out.println(threadLocal.get());

}, name+i);

thread.start();

}

Thread.sleep(20000);

}

}

// 輸出Thread-0

Thread-4

Thread-3

Thread-6

Thread-2

Thread-1

Thread-7

。。。

InheritableThreadLocal的使用

使用類InheritableThreadLocal可以在子線程中擷取父線程繼承下來的值。

public class InheritableThreadLocalTest extends InheritableThreadLocal {

@Override

protected Object childValue(Object parentValue) {

return super.childValue(parentValue);

}

@Override

protected Object initialValue() {

return System.currentTimeMillis();

}

}

* @date 2019/6/18 8:28

* @description

*/

public class InheritableTeat {

static public class Inner{

public static InheritableThreadLocalTest threadLocalTest = new InheritableThreadLocalTest();

}

public static void main(String[] args) throws InterruptedException{

for (int i = 0; i<3; i++){

System.out.println("在main線程中擷取值:"+ Inner.threadLocalTest.get());

}

for (int i=0; i<3; i++){

new Thread(() -> {

System.out.println("在"+Thread.currentThread().getName()+"中擷取值:"+ Inner.threadLocalTest.get());

}, "Thread-"+i).start();

}

Thread.sleep(1000);

}

}

// 輸出在main線程中擷取值:1560818029616

在main線程中擷取值:1560818029616

在main線程中擷取值:1560818029616

在Thread-1中擷取值:1560818029616

在Thread-2中擷取值:1560818029616

在Thread-0中擷取值:1560818029616

在使用InheritableThreadLocal類需要注意的一點是:如果子線程在取得值的同時,主線程将InheritableThreadLocal中的值進行更改,那麼子線程取到的還是舊值。