等待通知機制的實作
方法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-codegithub.com
方法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中的值進行更改,那麼子線程取到的還是舊值。