Java多線程學習(五)——等待通知機制

等待通知機制的實現

方法wait()的做用是使當前線程進行等待,wait()方法是Object類的方法,該方法用來將當前線程放到「預執行隊列」,並在wait()所在的代碼處中止執行,直到接到通知或中斷爲止。只能在同步方法或同步快中使用wait()方法,執行wait()後,當前線程釋放鎖。java

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

在執行notify()後當前線程不會立刻釋放鎖,會在線程退出synchronized代碼塊纔會釋放鎖,呈wait狀態的線程才能夠獲取鎖。當第一個獲取對象鎖的wait線程運行結束釋放鎖後,若是該對象沒有再次notify,其餘wait狀態的線程依然會阻塞wait狀態,直到這個對象發出notify或notifyAll。github

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後的方法。bash

相關方法

  • wait() :使調用該方法的線程釋放共享資源鎖,而後從運行狀態退出,進入等待隊列,直到被再次喚醒。
  • wait(long):超時等待一段時間,這裏的參數時間是毫秒,也就是等待長達n毫秒,若是沒有通知就超時返回。
  • notify():隨機喚醒等待隊列中等待同一共享資源的 「一個線程」,並使該線程退出等待隊列,進入可運行狀態,也就是notify()方法僅通知「一個線程」。
  • notifyAll():使全部正在等待隊列中等待同一共享資源的 「所有線程」 退出等待隊列,進入可運行狀態。此時,優先級最高的那個線程最早執行,但也有多是隨機執行,這取決於JVM虛擬機的實現。

線程的基本狀態

轉載與https://blog.csdn.net/qq_34337272/article/details/79690279

  1. 新建(new):新建立了一個線程對象。微信

  2. 可運行(runnable):線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲 取cpu的使用權。ide

  3. 運行(running):可運行狀態(runnable)的線程得到了cpu時間片(timeslice),執行程序代碼。ui

  4. 阻塞(block):阻塞狀態是指線程由於某種緣由放棄了cpu使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有 機會再次得到cpu timeslice轉到運行(running)狀態。阻塞的狀況分三種:this

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

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

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

本節代碼GitHub

方法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<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException{
        int count = 30;
        String name = "Thread-";
        for (int i=0; i<count; 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中的值進行更改,那麼子線程取到的仍是舊值。


歡迎關注公衆號:

公衆號微信
相關文章
相關標籤/搜索