Java多線程和併發問題集

併發是指什麼

併發是程序同時執行多個計算的能力。 這能夠經過將計算分佈在機器的可用CPU內核上(多核CPU支持)或甚至經過同一網絡內的不一樣機器來實現(後臺分佈式)。java

進程和線程有什麼區別

進程是操做系統提供的執行環境,具備本身的一組專用資源(例如內存,打開的文件等)。 線程相對進程而言,線程存活在一個進程中,並與進程的其餘線程共享其資源(內存,打開的文件等)。 在不一樣線程之間共享資源的能力使線程更適合於對性能要求的任務。android

在Java中,什麼是進程和線程

在Java中,進程對應於正在運行的Java虛擬機(JVM),而線程駐留在JVM中,而且能夠在運行時動態地由Java應用程序建立和中止。c++

什麼是 scheduler(調度程序)

scheduler是一種調度算法的實現,它管理進程和線程對處理器或某些I / O通道等有限資源的訪問。 大多數調度算法的目標是爲可用進程/線程提供某種負載均衡,以確保每一個進程/線程得到適當的時間片來專門訪問請求的資源。程序員

一個Java程序至少有多少個線程

每一個Java程序都在主線程中執行; 所以每一個Java應用程序至少有一個線程。算法

Java應用程序如何訪問當前線程

當前線程能夠經過調用JDK中提供的類java.lang.Thread的靜態方法currentThread()來訪問:數據庫

public class MainThread {
    public static void main(String[] args) {
        long id = Thread.currentThread().getId();
        String name = Thread.currentThread().getName();
        ...
    }
}
複製代碼

每一個Java線程都有哪些屬性

每一個Java線程都有如下屬性:編程

  • JVM中惟一的long類型標識符
  • String類型的名稱
  • int類型的優先級
  • 類型爲java.lang.Thread.State的狀態
  • 線程所屬的線程組

線程組的目的是什麼

每一個線程都屬於一組線程。 JDK類java.lang.ThreadGroup提供了一些方法來處理整組線程。 例如,經過這些方法,能夠中斷線程組的全部線程或設置其最大優先級。數組

線程能夠擁有哪些狀態以及每一個狀態的含義

  • NEW:還沒有啓動的線程處於此狀態。
  • RUNNABLE:在Java虛擬機中執行的線程處於此狀態。
  • BLOCKED:阻塞等待監視器鎖定的線程處於此狀態。
  • WAITING:無限期地等待另外一個線程執行特定動做的線程處於這種狀態。
  • TIMED_WAITING:正在等待另外一個線程執行動做達到指定等待時間的線程處於此狀態。
  • TERMINATED:已退出的線程處於此狀態。

咱們如何設置線程的優先級

線程的優先級經過setPriority(int)方法設置。 要將優先級設置爲最大值,咱們使用常量Thread.MAX_PRIORITY。要將其設置爲最小值,咱們使用常量Thread.MIN_PRIORITY,由於這些值在不一樣的JVM實現之間可能會有所不一樣。緩存

Java中如何建立一個線程

基本上,有兩種方法能夠在Java中建立線程。 第一個是編寫一個擴展JDK類java.lang.Thread並調用其方法start()的類:安全

public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println("Executing thread "+Thread.currentThread().getName());
    }
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread("myThread");
        myThread.start();
    }
}
複製代碼

第二種方法是實現接口java.lang.Runnable並將此實現做爲參數傳遞給java.lang.Thread的構造函數:

public class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Executing thread "+Thread.currentThread().getName());
    }
    public static void main(String[] args) throws InterruptedException {
        Thread myThread = new Thread(new MyRunnable(), "myRunnable");
        myThread.start();
    }
}
複製代碼

咱們如何中止Java中的線程

public class StopThread {
    public static void main(String[] arg) throws InterruptedException {
        MyStopThread myStopThread = new MyStopThread();
        myStopThread.start();
        Thread.sleep(1000 * 5);
        myStopThread.stopThread();
    }

    private static class MyStopThread extends Thread {
        private volatile Thread stopIndicator;

        public void start() {
            stopIndicator = new Thread(this);
            stopIndicator.start();
        }

        public void stopThread() {
            stopIndicator = null;
        }

        @Override
        public void run() {
            Thread thisThread = Thread.currentThread();
            while (thisThread == stopIndicator) {
                try {
                    System.out.println("wait...");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
複製代碼

輸出內容:

爲何一個線程不能經過調用stop()方法來中止

不該該使用java.lang.Thread的廢棄方法stop()中止線程,由於此方法的調用會致使線程解鎖其已獲取的全部監視器。 若是任何一個由釋放鎖保護的對象處於不一致狀態,則此狀態對全部其餘線程均可見。 當其餘線程處理這個不一致的對象時,這可能致使不可測的行爲。

是否有可能啓動一個線程兩次

不能,在經過調用start()方法啓動線程後,第二次調用start()將拋出IllegalT hreadStateException異常。

如下代碼的輸出是什麼

public class MultiThreading {
    private static class MyThread extends Thread {
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
        public static void main(String[] args) {
            MyThread myThread = new MyThread("myThread");
            myThread.run();
        }
    }
}
複製代碼

上面的代碼輸出「main」而不是「myThread」。 從main()方法的第二行能夠看出,咱們錯誤地調用run()方法而不是start()。 所以,沒有新的線程啓動,run()方法依舊在主線程中執行。

什麼是守護線程

當全部用戶線程(與守護線程相對)都終止時,JVM纔會中止。當JVM決定是否中止時,不會考慮到守護線程的執行狀態。 所以,守護線程能夠用於實現監視功能,只要全部用戶線程都中止了,守護線程就會被JVM中止:

public class Example {
    private static class MyDaemonThread extends Thread {
        public MyDaemonThread() {
            setDaemon(true);
        }
        @Override
        public void run() {
            while (true) {
                try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            }
    }
    public static void main(String[] args) throws InterruptedException {
                Thread thread = new MyDaemonThread();
                thread.start();
    }
}
複製代碼

上面的示例應用程序將當即終止,即便守護程序線程仍在其while循環中運行。

是否有可能在普通用戶線程啓動後將其轉換爲守護線程

用戶線程一旦啓動就沒法轉換爲守護線程。 在已經運行的線程實例上調用thread.setDaemon( true) 方法會致使IllegalThreadStateException異常。

busy waiting 告訴咱們什麼

busy waiting 意味着經過執行一些主動計算來等待事件的實現,這些計算使線程/進程佔用處理器,儘管它已經能夠被調度程序從中移除。 busy waiting 的一個例子是在循環內花費等待時間,該循環一次又一次地肯定當前時間,直到達到某個時間點:

Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
                long millisToStop = System.currentTimeMillis() + 5000;
                long currentTimeMillis = System.currentTimeMillis();
                while (millisToStop > currentTimeMillis) {
                    currentTimeMillis = System.currentTimeMillis();
                } 
        }
});
複製代碼

咱們如何防止 busy waiting

防止 busy waiting 的一種方法是將當前線程休眠一段給定的時間。 這能夠經過調用方法java.lang.Thread.sleep(long) 來完成,將毫秒數做爲參數休眠。

咱們可使用Thread.sleep()進行實時處理嗎

傳遞給Thread.sleep(long) 調用的毫秒數只是scheduler指示當前線程不須要執行多長時間。 根據實際的實現狀況,scheduler可能會讓線程再提早幾毫秒執行一次。 所以,Thread.sleep()的調用不該該用於實時處理。

如何在使用Thread.sleep()以前將線程喚醒

java.lang.Thread的interrupt()方法中斷正在睡眠的線程。 已經過調用Thread.sleep() 進入睡眠狀態的中斷線程被InterruptedException喚醒:

public class InterruptThread implements Runnable {
    public static void main(String[] arg) throws InterruptedException {
        Thread myThread = new Thread(new InterruptThread(), "myThread");
        myThread.start();

        System.out.println("[" + Thread.currentThread().getName() + "] Sleeping in main ← thread for 5s...");
        Thread.sleep(5000);

        System.out.println("[" + Thread.currentThread().getName() + "] Interrupting ← myThread");
        myThread.interrupt();
    }

    @Override
    public void run() {
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("[" + Thread.currentThread().getName() + "] ← Interrupted by exception!");
        }
    }
}
複製代碼

輸出內容:

[main] Sleeping in main ←  thread for 5s...
[main] Interrupting ←  myThread
[myThread] ←  Interrupted by exception!
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.zpw.test.thread.InterruptThread.run(InterruptThread.java:23)
	at java.lang.Thread.run(Thread.java:745)

Process finished with exit code 0
複製代碼

一個線程如何查詢它是否被中斷

若是線程不在像Thread.sleep() 這樣會拋出InterruptedException的方法內,線程能夠經過調用從java.lang.Thread繼承的靜態方法Thread.interrupted() 或方法isInterrupted() 來查詢它是否已被中斷 。

應該如何處理InterruptedException

像sleep()和join()這樣的方法會拋出一個InterruptedException來告訴調用者另外一個線程已經中斷了這個線程。 在大多數狀況下,這是爲了告訴當前線程中止當前的計算並以異常的方式完成它們。 所以,經過捕獲異常並僅將其記錄到控制檯或某些日誌文件來忽略異常一般不是處理這種異常的適當方式。 這個異常的問題是,Runnable接口的run()方法不容許run()拋出任何異常。 因此從新拋出它並無意義。 這意味着run()的實現必須本身處理這個檢查的異常,這一般會致使它被捕獲並被忽略的事實。

在啓動一個子線程以後,咱們如何在父線程中等待子線程的終止

等待線程終止是經過調用線程實例變量上的join()方法來完成的:

Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
        }
});
thread.start();
thread.join();
複製代碼

如下程序的輸出是什麼

public class MyThreads {
    private static class MyDaemonThread extends Thread {
        public MyDaemonThread() {
                setDaemon(true);
        }
        @Override
        public void run() {
        try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyDaemonThread();
        thread.start();
        thread.join();
        System.out.println(thread.isAlive());
    }
}
複製代碼

上述代碼的輸出是「false」。 儘管MyDaemonThread的實例是一個守護線程,但調用join()會致使主線程等待,直到守護線程的執行完成。 所以,在線程實例上調用isAlive()會發現守護線程再也不運行。

當未捕獲的異常離開run()方法時會發生什麼

可能會發生一個未經檢查的異常從run() 方法中逃脫。 在這種狀況下,線程被Java虛擬機中止。 能夠經過註冊一個實現接口UncaughtExceptionHandler的實例做爲異常處理程序來捕獲此異常。 這能夠經過調用靜態方法Thread.setDefaultUncaughtExceptionHandler(Thread.Unc aughtExceptionHandler)來完成,該方法告訴JVM在線程自己沒有註冊特定處理程序的狀況下使用提供的處理程序,或經過在線程實例自己調用setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)。

什麼是 shutdown hook

shutdown hook 是在JVM關閉時執行的線程。 它能夠經過在Runtime實例上調用addShutdownHook(Runnable)來註冊:

Runtime.getRuntime().addShutdownHook(new Thread() {
    @Override
    public void run() {
    }
});
複製代碼

關於 synchronized 關鍵字的用途

當你必須實現對某個資源的獨佔訪問時,如某些靜態值或某個文件引用,與獨佔資源一塊兒工做的代碼能夠包含一個 synchronized 塊:

synchronized (SynchronizedCounter.class) {
    counter++;
}
複製代碼

同步方法得到誰的內在鎖

synchronized方法獲取該方法對象的內部鎖並在方法返回時釋放它。 即便該方法拋出異常,內部鎖也被釋放。 所以,一個同步方法等於如下代碼:

public void method() {
    synchronized(this) {
        ...
    }
}
複製代碼

構造函數是否能夠同步

構造函數不能同步。 致使語法錯誤的緣由是隻有構造線程才能訪問正在構建的對象。

基本類型變量能夠用作內部鎖嗎

基本類型變量不能用作內部鎖。

內在鎖可重入嗎

能夠。內部鎖能夠一次又一次地被相同的線程訪問。 不然,獲取鎖的代碼將不得不注意,它不會意外地嘗試獲取它已獲取的鎖。

經過原子操做了解什麼

原子操做要麼徹底執行,要麼根本不執行。

語句c++是原子性的嗎

不是,整數變量的增量由多個操做組成。 首先,咱們必須加載c的當前值,而後增長它,而後最後將新值存回。 執行此增量的當前線程可能會在這三個步驟中的任何一個之間中斷,所以此操做不是原子操做。

Java中的原子操做是什麼

Java語言提供了一些基本的操做,所以可用於確保併發線程始終看到相同的值:

  • 以引用變量和原始變量(long和double除外)的讀取和寫入操做
  • 對全部聲明爲volatile的變量進行讀寫操做

如下實現是線程安全的

public class DoubleCheckedSingleton {
        private DoubleCheckedSingleton instance = null;
        public DoubleCheckedSingleton getInstance() {
                if(instance == null) {
                    synchronized (DoubleCheckedSingleton.class) {
                        if(instance == null) {
                            instance = new DoubleCheckedSingleton();
                        }
                    }
                }
                return instance;
        }
}
複製代碼

上面的代碼不是線程安全的。 儘管它在同步塊內再次檢查實例的值(出於性能緣由),但JIT編譯器能夠從新排列字節碼,以便在構造函數完成其執行以前設置對實例的引用。 這意味着getInstance()方法返回一個可能還沒有徹底初始化的對象。 爲了讓代碼是線程安全的,關鍵字volatile能夠在Java 5之後用於實例變量。 標記爲volatile的變量只有在對象的構造函數完成完成後纔會被其餘線程看到。

deadlock 是什麼

死鎖是兩個(或更多)線程在等待另外一個線程釋放已鎖定的資源的狀況,而線程自己鎖定了另外一個線程正在等待的資源:線程1:鎖定資源A ,等待資源B;線程2:鎖定資源B,等待資源A。

死鎖發生的前提

一般能夠肯定如下死鎖要求:

  • 相互排斥:有一種資源只能在任什麼時候間點由一個線程訪問。
  • 資源保持:當鎖定一個資源時,該線程試圖獲取另外一個獨佔資源上的另外一個鎖。
  • 不搶佔:沒有任何機制,若是某個線程在特定時間段內持有鎖,則釋放該資源。
  • 循環等待:在運行時期間會出現一個羣集,其中兩個(或更多)線程互相都在另外一個線程上等待以釋放它已鎖定的資源。

是否有可能防止死鎖

爲了防止死鎖,必須消除一個(或多個)死鎖要求:

  • 相互排斥:在某些狀況下,能夠經過使用樂觀鎖定來防止相互排斥。
  • 資源保存:當線程沒法得到全部排他鎖時,它可能會釋放其全部排他鎖。
  • 不搶佔:對排他鎖使用超時在給定時間後釋放鎖。
  • 循環等待:當全部線程以相同順序得到全部排它鎖時,不會發生循環等待。

是否有可能實現死鎖檢測

當全部獨佔鎖都被監視並建模爲定向圖時,死鎖檢測系統能夠搜索兩個線程,每一個線程都在另外一個線程上等待以釋放它已鎖定的資源。 等待的線程而後能夠被某種異常強制釋放另外一個線程正在等待的鎖。

什麼是活鎖

活鎖是兩個或多個線程經過響應由另外一個線程引發的操做而彼此阻塞的狀況。 與死鎖狀況相反,兩個或更多線程在一個特定狀態下等待,參與活鎖的線程以阻止正常工做進度的方式更改其狀態。 一個例子就是兩個線程試圖得到兩個鎖的狀況,可是當他們沒法得到第二個鎖時釋放第一個得到的鎖。 如今可能發生兩個線程同時嘗試獲取第一個線程。 因爲只有一個線程成功,第二個線程可能成功獲取第二個鎖。 如今兩個線程都擁有兩個不一樣的鎖,但因爲二者都想擁有這兩個鎖,它們釋放它們的鎖並從頭開始重試。 這種狀況可能會一次又一次地發生。

咱們經過線程飢餓瞭解什麼

具備較低優先級的線程比具備較高優先級的線程得到較少的執行時間。 當優先級較低的線程執行長期持久的計算時,可能會發生這些線程沒有足夠的時間來及時完成其計算。 他們彷佛「餓死」,由於具備更高優先級的線程竊取他們的計算時間。

同步塊可能致使線程飢餓

沒有定義線程能夠進入同步塊的順序。 因此理論上可能發生的狀況是,若是許多線程正在等待同步塊的入口,則某些線程必須等待比其餘線程更長的時間。 所以他們沒有足夠的計算時間來及時完成工做。

術語競賽條件來理解什麼

競爭條件描述了一些現象,其中一些多線程實現的結果取決於參與線程的確切時間行爲。 在大多數狀況下,不但願出現這種行爲,所以術語競爭條件也意味着因爲缺乏線程同步而致使的錯誤會致使不一樣的結果。 一個競爭條件的簡單例子是由兩個併發線程增長一個整數變量。 因爲該操做由多個單一操做和原子操做組成,所以可能會發生這兩個線程讀取並遞增相同的值。 在這個併發增量以後,整數變量的數量不會增長2,而只會增長1。

公平鎖是什麼

當選擇下一個將屏障傳遞給某個獨佔資源的線程時,公平鎖會將線程的等待時間考慮在內。 Java SDK提供了一個公平鎖的示例實現:java.util.concurrent。locks.ReentrantLock。 經過將構造函數使用布爾標誌設置爲true,則ReentrantLock授予訪問最長等待線程的權限。

每一個對象從java.lang.Object繼承的哪兩種方法可用於實現簡單的生產者/消費者方案

當工做線程完成當前任務而且新任務的隊列爲空時,它能夠經過獲取隊列對象的內部鎖並經過調用方法wait()來釋放處理器。 該線程將被某個生產者線程喚醒,該線程已將新任務放入隊列中,並再次獲取隊列對象上的相同內部鎖並調用notify()。

notify()和notifyAll()有什麼區別

這兩種方法都用來喚醒一個或多個經過調用wait()使本身進入睡眠狀態的線程。 雖然notify()只喚醒其中一個等待的線程,notifyAll()喚醒全部等待的線程。

如何經過調用notify()來肯定哪一個線程被喚醒

若是有多個線程正在等待,則不會指定哪一個線程將經過調用notify()來喚醒。 所以,代碼不該該依賴任何具體的JVM實現。

如下代碼是從某個隊列實現中檢索整數值的正確方法嗎

public Integer getNextInt() {
    Integer retVal = null;
    synchronized (queue) {
        try {
            while (queue.isEmpty()) {
                queue.wait();
            }
        } catch (InterruptedException e) {
        }
    }
    synchronized (queue) {
        retVal = queue.poll();
        if (retVal == null) {
            System.err.println("retVal is null");
            throw new IllegalStateException();
        }
    }
    return retVal;
}
複製代碼

儘管上面的代碼使用隊列做爲對象監視器,但它在多線程環境中的行爲不正確。 緣由是它有兩個獨立的同步塊。 當另外一個調用notifyAll()的線程在第6行喚醒兩個線程時,兩個線程都會相繼輸入第二個同步塊。它的第二個塊如今只有一個新的值,所以第二個線程將輪詢一個空的隊列並將null做爲返回值。

是否有可能檢查某個線程是否對某個給定對象持有監視器鎖

類java.lang.Thread提供了返回true的靜態方法Thread.holdsLock(Object) ,當且僅當當前線程持有做爲方法調用的參數給定的對象上的鎖時才返回true。

Thread.yield()方法的做用

對靜態方法Thread.yield() 的調用爲調度器提供了一條提示,即當前線程願意釋放處理器。 調度器能夠自由地忽略這個提示。 因爲沒有定義哪一個線程在調用Thread.yield()後會獲得處理器,所以甚至可能發生當前線程變爲要執行的「下一個」線程。

將對象實例從一個線程傳遞到另外一個線程時須要考慮什麼

在線程之間傳遞對象時,必須注意這些對象不能同時由兩個線程操縱。 一個例子是一個Map實現,其鍵/值對由兩個併發線程修改。 爲了不併發修改的問題,你能夠設計一個對象爲不可變的。

爲了實現一個不可變的類,你必須遵循哪些規則

  • 全部字段應該是final的和private。
  • 不該該有setter方法。
  • 爲了防止子類違反不可變性原則,類自己應該被宣佈爲final。
  • 若是字段不是原始類型,而是對另外一個對象的引用:
    • 不該該有一個getter方法將參考直接暴露給調用者。
    • 不要改變引用的對象(或者至少改變這些引用對對象的客戶端不可見)。

類java.lang.ThreadLocal的目的是什麼

因爲內存在不一樣的線程之間共享,ThreadLocal提供了一種爲每一個線程單獨存儲和檢索值的方法。 ThreadLocal的實現存儲併爲每一個線程獨立檢索值,以便當線程A存儲值A1而且線程B將值B1存儲在同一個ThreadLocal實例中時,線程A稍後今後ThreadLocal實例中檢索值A1,而且線程B檢索值B1。

java.lang.ThreadLocal有哪些可能的用例

ThreadLocal的實例可用於在整個應用程序中傳輸信息,而無需將它由方法傳遞給方法。 例子就是在ThreadLocal的一個實例中傳輸安全/登陸信息,這樣每一個方法均可以訪問它。 另外一個用例是傳輸事務信息或通常對象,這些對象應該能夠在全部方法中訪問,而無需將它們從方法傳遞到方法。

是否能夠經過使用多線程來提升應用程序的性能

若是咱們有多個CPU內核可用,若是能夠經過可用的CPU內核對計算進行並行化,則能夠經過多線程來提升應用程序的性能。 一個例子是縮放存儲在本地目錄結構中的全部圖像的應用程序。 生產者/消費者實現可使用單個線程來掃描目錄結構以及執行實際縮放操做的一羣工做線程,而不是一個接一個地遍歷全部映像。 另外一個例子是一個映射網頁的應用程序。 生產者線程能夠解析第一個HTML頁面,並將它找到的連接發佈到隊列中,而不是一個接一個加載HTML頁面。 工做線程監視隊列並加載解析器找到的網頁。 當工做線程等待頁面徹底加載時,其餘線程可使用CPU來解析已加載的頁面併發出新的請求。

術語可伸縮性表明什麼

可伸縮性意味着程序經過增長更多資源來提升性能的能力。

是否有可能經過使用多個處理器來計算應用程序的理論最大加速度

Amdahl’s law provides a formula to compute the theoretical maximum speed up by providing multiple processors to an applica- tion.ThetheoreticalspeedupiscomputedbyS(n) =1 /(B + (1-B)/n)wherendenotesthenumberofprocessorsandB the fraction of the program that cannot be executed in parallel. When n converges against infinity, the term (1-B)/n converges against zero. Hence the formula can be reduced in this special case to 1/B. As we can see, the theoretical maximum speedup behaves reciprocal to the fraction that has to be executed serially. This means the lower this fraction is, the more theoretical speedup can be achieved.

鎖爭奪

當兩個或兩個以上的線程競爭鎖時,會發生鎖爭奪。 調度器必須決定它是否容許線程等待休眠,並執行上下文切換以讓另外一個線程佔用CPU,或讓等待線程 busy-waiting效率更高。 兩種方式都將空閒時間引入劣質線程。

減小鎖爭奪

在某些狀況下,經過應用如下技術之一能夠減小鎖爭用:

  • 鎖的範圍縮小。
  • 獲取特定鎖的次數減小(鎖分割)。
  • 使用硬件支持的樂觀鎖操做而不是同步。
  • 儘量避免同步。
  • 避免使用對象池。

下面的代碼能夠應用哪一種技術來減小鎖爭奪

synchronized (map) {
    UUID randomUUID = UUID.randomUUID();
    Integer value = Integer.valueOf(42);
    String key = randomUUID.toString();
    map.put(key, value);
}
複製代碼

上面的代碼執行隨機UUID的計算以及將文字42轉換爲同步塊內的Integer對象,儘管這兩行代碼對當前線程是本地的而且不影響其餘線程。 所以能夠將它們移出同步塊:

UUID randomUUID = UUID.randomUUID();
Integer value = Integer.valueOf(42);
String key = randomUUID.toString();
synchronized (map) {
    map.put(key, value);
}
複製代碼

鎖分裂技術

當使用一個鎖來同步對相同應用程序的不一樣方面的訪問時,鎖分割多是減小鎖爭用的一種方式。 假設咱們有一個類來實現咱們應用程序的一些統計數據的計算。 該類的第一個版本在每一個方法簽名中使用關鍵字synchronized,以便在多個併發線程損壞以前保護內部狀態。 這也意味着每一個方法調用均可能致使鎖爭用,由於其餘線程可能會嘗試同時獲取相同的鎖。 可是也能夠將對象實例上的鎖分爲每種方法中每種統計數據的幾個較小的鎖。 所以,嘗試遞增統計數據D1的線程T1在線程T2同時更新數據D2的同時沒必要等待鎖。

SDK類ReadWriteLock使用了哪一種減小鎖爭用的技術

SDK類ReadWriteLock使用這樣一個技術,即若是沒有其餘線程嘗試更新值時,併發線程不須要獲取鎖就能夠讀取值。 這是經過一對鎖實現的,一個用於只讀操做,另外一個用於寫入操做。 雖然只讀鎖能夠經過多個線程得到,可是實現保證了一旦寫入鎖被釋放,全部讀操做看到更新的值。

鎖條紋技術

在鎖分割中,咱們爲應用程序的不一樣方面引入了不一樣的鎖,與鎖分割相反,鎖條紋使用多個鎖來保護相同數據結構的不一樣部分。 此技術的一個示例是JDK的java.util.concurrent包中的類ConcurrentHashMap。 Map實現使用內部不一樣的桶來存儲其值。 存儲桶由值的鍵選擇。 ConcurrentHashMap如今使用不一樣的鎖來保護不一樣的散列桶。 所以,一個嘗試訪問第一個哈希桶的線程能夠獲取該桶的鎖,而另外一個線程能夠同時訪問第二個桶。 與HashMap的同步版本相比,此技術能夠在不一樣線程在不一樣存儲桶上工做時提升性能。

CAS操做

CAS表明比較和交換,意味着處理器提供了一個單獨的指令,只有當提供的值等於當前值時才更新寄存器的值。 CAS操做能夠用來避免同步,由於線程能夠經過向CAS操做提供當前值和新值來嘗試更新值。 若是另外一個線程同時更新了該值,則該線程的值不等於當前值,而且更新操做失敗。 線程而後讀取新值並再次嘗試。 這種方式經過樂觀的自旋等待交換了必要的同步。

哪些Java類使用CAS操做

包java.util.concurrent.atomic中的SDK類(如AtomicInteger或AtomicBoolean)在內部使用CAS操做來實現併發增量。

public class CounterAtomic {
    private AtomicLong counter = new AtomicLong();

    public void increment() {
        counter.incrementAndGet();
    }

    public long get() {
        return counter.get();
    }
}
複製代碼

提供一個例子說明爲何單線程應用程序的性能改進會致使多線程應用程序的性能降低

這種優化的一個突出例子是List實現,它將元素的數量保存爲一個單獨的變量。 這能夠提升單線程應用程序的性能,由於size()操做沒必要遍歷全部元素,但能夠直接返回當前元素數。 在多線程應用程序中,附加計數器必須由鎖保護,由於多個併發線程可能會將元素插入到列表中。 當列表的更新數量多於size()操做的調用時,額外鎖可能會下降性能。

對象池老是對多線程應用程序的性能改進

避免建新對象的對象池能夠提升單線程應用程序的性能,由於經過向池中請求新對象來交換對象建立成本。 在多線程應用程序中,這樣的對象池必須具備對池的同步訪問權限,而且鎖爭奪的額外成本可能會超過額外構建和垃圾收集新對象所節省的成本。 所以,對象池並不老是能夠提升多線程應用程序的總體性能。

接口Executor和ExecutorServices之間的關係

接口Executor只定義了一個方法:execute(Runnable)。 此接口的實現將不得不在將來的某個時間執行給定的Runnable實例。 ExecutorService接口是Executor接口的擴展,提供了關閉底層實現的其餘方法,以等待終止全部提交的任務,並容許提交Callable實例。

將新任務submit()給ExecutorService實例(其隊列已滿)時會發生什麼狀況

由submit()的方法簽名指示,ExecutorService實現應該拋出Rejected ExecutionException異常。

ScheduledExecutorService

接口ScheduledExecutorService擴展了接口ExecutorService,並添加了容許將新任務提交給應該在給定時間點執行的底層實現的方法。 有兩種方法能夠調度一次性任務和兩種方法來建立和執行週期性任務。

構造一個帶有5個線程的線程池,它執行將會返回值的任務

SDK提供了一個工廠和實用類的Executors,它們經過靜態方法newFixedThreadPool(int nThreads)容許建立一個具備固定數量線程的線程池(MyCallable的實現被省略):

public static void main2(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    Future<Integer>[] futures = new Future[5];
    for (int i = 0; i < futures.length; i++) {
        futures[i] = executorService.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return Integer.valueOf(UUID.randomUUID().toString());
            }
        });
    }
    for (int i = 0; i < futures.length; i++) {
        Integer retVal = futures[i].get();
        System.out.println(retVal);
    }
    executorService.shutdown();
}
複製代碼

Runnable和Callable之間有什麼區別

Runnable接口定義了沒有任何返回值的方法run(),而Callable接口容許方法run()返回一個值並拋出一個異常。

java.util.concurrent.Future的用例

類java.util.concurrent.Future的實例用於表示異步計算的結果,其結果不是當即可用的。 所以,該類提供了檢查異步計算是否完成,取消任務和檢索實際結果的方法。 後者可使用提供的兩個get() 方法完成。 第一個get() 方法在結果可用以前不接受任何參數和塊,而第二個get() 方法接受一個超時參數,若是結果在給定時間範圍內不可用,則該方法調用將返回。

HashMap和Hashtable之間有什麼區別,特別是關於線程安全性

Hashtable的方法都是同步的。HashMap則不是。 所以Hashtable是線程安全的,而HashMap不是線程安全的。 對於單線程應用程序,使用HashMap實現更高效。

有沒有簡單的方法來建立一個任意實現的Collection,List或Map的同步實例

實用程序類Collections提供了返回給定實例支持的線程安全的collection/list/map的方法synchronizedCollection(Collection),synchronizedList(List)和synchronizedMap(Map) 。

Semaphore

信號量是一個數據結構,它維護一組必須經過競爭線程獲取的許可證。 所以可使用信號量來控制有多少線程同時訪問關鍵部分或資源。 所以,java.util.concurrent.Semaphore的構造函數將第一個參數做爲線程競爭許可的數量。 其acquire()方法的每次調用都會嘗試獲取其中一個可用的許可證。 方法acquire()沒有任何參數塊,直到下一個許可證可用。 稍後,當線程在關鍵資源上完成其工做時,它能夠經過調用Semaphore實例上的方法release()來釋放許可證。

信號量維護一個許可集,可經過acquire()獲取許可(若無可用許可則阻塞),經過release()釋放許可,從而可能喚醒一個阻塞等待許可的線程。

與互斥鎖相似,信號量限制了同一時間訪問臨界資源的線程的個數,而且信號量也分公平信號量與非公平信號量。而不一樣的是,互斥鎖保證同一時間只會有一個線程訪問臨界資源,而信號量能夠容許同一時間多個線程訪問特定資源。因此信號量並不能保證原子性。

信號量的一個典型使用場景是限制系統訪問量。每一個請求進來後,處理以前都經過acquire獲取許可,若獲取許可成功則處理該請求,若獲取失敗則等待處理或者直接不處理該請求。

信號量的使用方法

  • acquire(int permits) 申請permits(必須爲非負數)個許可,若獲取成功,則該方法返回而且當前可用許可數減permits;若當前可用許可數少於permits指定的個數,則繼續等待可用許可數大於等於permits;若等待過程當中當前線程被中斷,則拋出InterruptedException。
  • acquire() 等價於acquire(1)。
  • acquireUninterruptibly(int permits) 申請permits(必須爲非負數)個許可,若獲取成功,則該方法返回而且當前可用許可數減permits;若當前許可數少於permits,則繼續等待可用許可數大於等於permits;若等待過程當中當前線程被中斷,繼續等待可用許可數大於等於permits,而且獲取成功後設置線程中斷狀態。
  • acquireUninterruptibly() 等價於acquireUninterruptibly(1)。
  • drainPermits() 獲取全部可用許可,並返回獲取到的許可個數,該方法不阻塞。
  • tryAcquire(int permits) 嘗試獲取permits個可用許可,若是當前許可個數大於等於permits,則返回true而且可用許可數減permits;不然返回false而且可用許可數不變。
  • tryAcquire() 等價於tryAcquire(1)。
  • tryAcquire(int permits, long timeout, TimeUnit unit) 嘗試獲取permits(必須爲非負數)個許可,若在指定時間內獲取成功則返回true而且可用許可數減permits;若指定時間內當前線程被中斷,則拋出InterruptedException;若指定時間內可用許可數均小於permits,則返回false。
  • tryAcquire(long timeout, TimeUnit unit) 等價於tryAcquire(1, long timeout, TimeUnit unit)*
  • release(int permits) 釋放permits個許可,該方法不阻塞而且某線程調用release方法前並不須要先調用acquire方法。
  • release() 等價於release(1)。

注意:與wait/notify和await/signal不一樣,acquire/release徹底與鎖無關,所以acquire等待過程當中,可用許可知足要求時acquire可當即返回,而不用像鎖的wait和條件變量的await那樣從新獲取鎖才能返回。或者能夠理解成,只要可用許可知足需求,就已經得到了鎖。

CountDownLatch

SDK類CountDownLatch提供了一個同步輔助工具,可用於實現線程必須等待其餘線程達到相同狀態以便全部線程均可以啓動的場景。 這是經過提供一個減量的同步計數器來完成的,直到達到零值。 CountDownLatch實例達到零後,全部線程均可以繼續。 這能夠用來讓全部線程在給定的時間點啓動,方法是使用計數器的值1或等待多個線程完成。 在後一種狀況下,計數器用線程數進行初始化,每一個完成其工做的線程將鎖存器計數一次。

Java多線程編程中常常會碰到這樣一種場景——某個線程須要等待一個或多個線程操做結束(或達到某種狀態)纔開始執行。好比開發一個併發測試工具時,主線程須要等到全部測試線程均執行完成再開始統計總共耗費的時間,此時能夠經過CountDownLatch輕鬆實現。

public class CountDownLatchDemo {
  public static void main(String[] args) throws InterruptedException {
    int totalThread = 3;
    long start = System.currentTimeMillis();
    CountDownLatch countDown = new CountDownLatch(totalThread);
    for(int i = 0; i < totalThread; i++) {
      final String threadName = "Thread " + i;
      new Thread(() -> {
        System.out.println(String.format("%s\t%s %s", new Date(), threadName, "started"));
        try {
          Thread.sleep(1000);
        } catch (Exception ex) {
          ex.printStackTrace();
        }
        countDown.countDown();
        System.out.println(String.format("%s\t%s %s", new Date(), threadName, "ended"));
      }).start();;
    }
    countDown.await();
    long stop = System.currentTimeMillis();
    System.out.println(String.format("Total time : %sms", (stop - start)));
  }
}
複製代碼

執行結果

Sun Jun 19 20:34:31 CST 2016  Thread 1 started
Sun Jun 19 20:34:31 CST 2016  Thread 0 started
Sun Jun 19 20:34:31 CST 2016  Thread 2 started
Sun Jun 19 20:34:32 CST 2016  Thread 2 ended
Sun Jun 19 20:34:32 CST 2016  Thread 1 ended
Sun Jun 19 20:34:32 CST 2016  Thread 0 ended
Total time : 1072ms
複製代碼

能夠看到,主線程等待全部3個線程都執行結束後纔開始執行。

CountDownLatch工做原理相對簡單,能夠簡單當作一個倒計數器,在構造方法中指定初始值,每次調用countDown()方法時將計數器減1,而await()會等待計數器變爲0。CountDownLatch關鍵接口以下

  • countDown() 若是當前計數器的值大於1,則將其減1;若當前值爲1,則將其置爲0並喚醒全部經過await等待的線程;若當前值爲0,則什麼也不作直接返回。
  • await() 等待計數器的值爲0,若計數器的值爲0則該方法返回;若等待期間該線程被中斷,則拋出InterruptedException並清除該線程的中斷狀態。
  • await(long timeout, TimeUnit unit) 在指定的時間內等待計數器的值爲0,若在指定時間內計數器的值變爲0,則該方法返回true;若指定時間內計數器的值仍未變爲0,則返回false;若指定時間內計數器的值變爲0以前當前線程被中斷,則拋出InterruptedException並清除該線程的中斷狀態。
  • getCount() 讀取當前計數器的值,通常用於調試或者測試。

CyclicBarrier

內存屏障,它能保證屏障以前的代碼必定在屏障以後的代碼以前被執行。CyclicBarrier能夠譯爲循環屏障,也有相似的功能。CyclicBarrier能夠在構造時指定須要在屏障前執行await的個數,全部對await的調用都會等待,直到調用await的次數達到預約指,全部等待都會當即被喚醒。

從使用場景上來講,CyclicBarrier是讓多個線程互相等待某一事件的發生,而後同時被喚醒。而上文講的CountDownLatch是讓某一線程等待多個線程的狀態,而後該線程被喚醒。

public class CyclicBarrierDemo {
  public static void main(String[] args) {
    int totalThread = 5;
    CyclicBarrier barrier = new CyclicBarrier(totalThread);
    
    for(int i = 0; i < totalThread; i++) {
      String threadName = "Thread " + i;
      new Thread(() -> {
        System.out.println(String.format("%s\t%s %s", new Date(), threadName, " is waiting"));
        try {
          barrier.await();
        } catch (Exception ex) {
          ex.printStackTrace();
        }
        System.out.println(String.format("%s\t%s %s", new Date(), threadName, "ended"));
      }).start();
    }
  }
}
複製代碼

執行結果以下

Sun Jun 19 21:04:49 CST 2016  Thread 1  is waiting
Sun Jun 19 21:04:49 CST 2016  Thread 0  is waiting
Sun Jun 19 21:04:49 CST 2016  Thread 3  is waiting
Sun Jun 19 21:04:49 CST 2016  Thread 2  is waiting
Sun Jun 19 21:04:49 CST 2016  Thread 4  is waiting
Sun Jun 19 21:04:49 CST 2016  Thread 4 ended
Sun Jun 19 21:04:49 CST 2016  Thread 0 ended
Sun Jun 19 21:04:49 CST 2016  Thread 2 ended
Sun Jun 19 21:04:49 CST 2016  Thread 1 ended
Sun Jun 19 21:04:49 CST 2016  Thread 3 ended
複製代碼

從執行結果能夠看到,每一個線程都不會在其它全部線程執行await()方法前繼續執行,而等全部線程都執行await()方法後全部線程的等待都被喚醒從而繼續執行。

CyclicBarrier提供的關鍵方法以下

  • await() 等待其它參與方的到來(調用await())。若是當前調用是最後一個調用,則喚醒全部其它的線程的等待而且若是在構造CyclicBarrier時指定了action,當前線程會去執行該action,而後該方法返回該線程調用await的次序(getParties()-1說明該線程是第一個調用await的,0說明該線程是最後一個執行await的),接着該線程繼續執行await後的代碼;若是該調用不是最後一個調用,則阻塞等待;若是等待過程當中,當前線程被中斷,則拋出InterruptedException;若是等待過程當中,其它等待的線程被中斷,或者其它線程等待超時,或者該barrier被reset,或者當前線程在執行barrier構造時註冊的action時由於拋出異常而失敗,則拋出BrokenBarrierException。
  • await(long timeout, TimeUnit unit) 與await()惟一的不一樣點在於設置了等待超時時間,等待超時時會拋出TimeoutException。
  • reset() 該方法會將該barrier重置爲它的初始狀態,並使得全部對該barrier的await調用拋出BrokenBarrierException。

CountDownLatch和CyclicBarrier之間有什麼區別

兩個SDK類都在內部維護一個由不一樣線程遞減的計數器。 線程一直等到內部計數器達到0並今後處繼續。 可是與CountDownLatch相反,一旦值達到零CyclicBarrier類將內部值重置爲初始值。 因爲該名稱指示CyclicBarrier的實例所以能夠用於實現線程必須一次又一次地彼此等待的用例。

Phaser

CountDownLatch和CyclicBarrier都是JDK 1.5引入的,而Phaser是JDK 1.7引入的。Phaser的功能與CountDownLatch和CyclicBarrier有部分重疊,同時也提供了更豐富的語義和更靈活的用法。

Phaser顧名思義,與階段相關。Phaser比較適合這樣一種場景,一種任務能夠分爲多個階段,現但願多個線程去處理該批任務,對於每一個階段,多個線程能夠併發進行,可是但願保證只有前面一個階段的任務完成以後才能開始後面的任務。這種場景可使用多個CyclicBarrier來實現,每一個CyclicBarrier負責等待一個階段的任務所有完成。可是使用CyclicBarrier的缺點在於,須要明確知道總共有多少個階段,同時並行的任務數須要提早預約義好,且沒法動態修改。而Phaser可同時解決這兩個問題。

public class PhaserDemo {
  public static void main(String[] args) throws IOException {
    int parties = 3;
    int phases = 4;
    final Phaser phaser = new Phaser(parties) {
      @Override  
      protected boolean onAdvance(int phase, int registeredParties) {  
          System.out.println("====== Phase : " + phase + " ======");  
          return registeredParties == 0;  
      }  
    };
    
    for(int i = 0; i < parties; i++) {
      int threadId = i;
      Thread thread = new Thread(() -> {
        for(int phase = 0; phase < phases; phase++) {
          System.out.println(String.format("Thread %s, phase %s", threadId, phase));
          phaser.arriveAndAwaitAdvance();
        }
      });
      thread.start();
    }
  }
}
複製代碼

執行結果以下

Thread 0, phase 0
Thread 1, phase 0
Thread 2, phase 0
====== Phase : 0 ======
Thread 2, phase 1
Thread 0, phase 1
Thread 1, phase 1
====== Phase : 1 ======
Thread 1, phase 2
Thread 2, phase 2
Thread 0, phase 2
====== Phase : 2 ======
Thread 0, phase 3
Thread 1, phase 3
Thread 2, phase 3
====== Phase : 3 ======
複製代碼

從上面的結果能夠看到,多個線程必須等到其它線程的同一階段的任務所有完成才能進行到下一個階段,而且每當完成某一階段任務時,Phaser都會執行其onAdvance方法。

Phaser主要接口以下

  • arriveAndAwaitAdvance() 當前線程當前階段執行完畢,等待其它線程完成當前階段。若是當前線程是該階段最後一個未到達的,則該方法直接返回下一個階段的序號(階段序號從0開始),同時其它線程的該方法也返回下一個階段的序號。
  • arriveAndDeregister() 該方法當即返回下一階段的序號,而且其它線程須要等待的個數減一,而且把當前線程從以後須要等待的成員中移除。若是該Phaser是另一個Phaser的子Phaser(層次化Phaser會在後文中講到),而且該操做致使當前Phaser的成員數爲0,則該操做也會將當前Phaser從其父Phaser中移除。
  • arrive() 該方法不做任何等待,直接返回下一階段的序號。
  • awaitAdvance(int phase) 該方法等待某一階段執行完畢。若是當前階段不等於指定的階段或者該Phaser已經被終止,則當即返回。該階段數通常由arrive()方法或者arriveAndDeregister()方法返回。返回下一階段的序號,或者返回參數指定的值(若是該參數爲負數),或者直接返回當前階段序號(若是當前Phaser已經被終止)。
  • awaitAdvanceInterruptibly(int phase) 效果與awaitAdvance(int phase)至關,惟一的不一樣在於若該線程在該方法等待時被中斷,則該方法拋出InterruptedException。
  • awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit) 效果與awaitAdvanceInterruptibly(int phase)至關,區別在於若是超時則拋出TimeoutException。
  • bulkRegister(int parties) 註冊多個party。若是當前phaser已經被終止,則該方法無效,並返回負數。若是調用該方法時,onAdvance方法正在執行,則該方法等待其執行完畢。若是該Phaser有父Phaser則指定的party數大於0,且以前該Phaser的party數爲0,那麼該Phaser會被註冊到其父Phaser中。
  • forceTermination() 強制讓該Phaser進入終止狀態。已經註冊的party數不受影響。若是該Phaser有子Phaser,則其全部的子Phaser均進入終止狀態。若是該Phaser已經處於終止狀態,該方法調用不形成任何影響。

經過使用Fork / Join框架能夠解決哪些類型的任務

Fork / Join Framework基類java.util.concurrent.ForkJoinPool基本上是一個線程池,用於執行java.util.concurrent.ForkJoinTask的實例。 類ForkJoinTask提供了fork()和join()兩個方法。 雖然fork()用於啓動任務的異步執行,但join()方法用於等待計算結果。 所以Fork / Join框架能夠用來實現分治算法,其中一個更復雜的問題被分紅許多更小更容易解決的問題。

是否可使用Fork / Join-Framework在數組數組中找到最小的數字

在數字數組中尋找最小數字的問題能夠經過使用分而治之算法來解決。 能夠很容易解決的最小問題是兩個數字的數組,由於咱們能夠直接經過一個比較來肯定兩個數字中較小的一個。 使用分而治之的方法,初始數組被分紅兩部分長度相等,而且這兩部分都被提供給擴展類ForkJoinTask的兩個RecursiveTask實例。 經過分解它們執行的兩個任務並直接解決問題,若是它們的數組切片長度爲2,或者它們再次遞歸地將數組分割成兩部分並分叉兩個新的RecursiveTasks。 最後,每一個任務實例返回其結果(經過直接計算或等待兩個子任務)。 根任務而後返回數組中的最小數字。

RecursiveTask和Recursiveaction之間有什麼區別

與RecursiveTask相比,RecursiveAction的方法compute()沒必要返回值。 所以,當動做直接在某些數據結構上工做時,可使用RecursiveAction,而沒必要返回計算值。

是否有可能經過線程池在Java 8中執行流操做

集合提供了方法parallelStream()來建立一個由線程池處理的流。 或者能夠調用給定流上的中間方法parallel()來將順序流轉換爲並行對象。

如何訪問使用並行流操做的線程池

用於並行流操做的線程池能夠經過ForkJoinPool.commonPool()訪問。 這樣咱們就能夠用commonPool.getParallelism()來查詢它的並行級別。 該級別在運行時不能更改,但能夠經過提供如下JVM參數進行配置:-Djava.util.concurrent.ForkJoinPool.common. parallelism=5.

Executor 接口

用於執行提交給它的 Runnable 任務的對象。此接口提供了一種將任務提交與每一個任務的運行機制解耦的方法,包括線程使用,調度等的詳細信息。一般使用 Executor 而不是顯式建立線程。 例如,調用一組任務再也不使用

new Thread(new(RunnableTask())).start()
複製代碼

而是使用:

Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
複製代碼

可是,Executor 接口並不嚴格要求執行是異步的。 在最簡單的狀況下,executor 能夠當即在調用者的線程中運行提交的任務:

class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}
複製代碼

更典型地,任務在某個線程中執行而不是在調用者線程執行。 下面的 executor 爲每一個任務生成一個新線程。

class ThreadPerTaskExecutor implements Executor {
   public void execute(Runnable r) {
     new Thread(r).start();
   }
}
複製代碼

許多 Executor 實現類對如何以及什麼時候調度任務施加了某種限制。 下面的 executor 將任務提交序列化到第二個 executor,說明了一個複合執行程序。

class SerialExecutor implements Executor {
   final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
   final Executor executor;
   Runnable active;

   SerialExecutor(Executor executor) {
        this.executor = executor;
   }

   public synchronized void execute(final Runnable r) {
        tasks.offer(new Runnable() {
            public void run() {
                try {
                   r.run();
                } finally {
                    scheduleNext();
                }
           }
        });
        if (active == null) {
           scheduleNext();
        }
  }

   protected synchronized void scheduleNext() {
        if ((active = tasks.poll()) != null) {
           executor.execute(active);
        }
   }
}
複製代碼

內存一致性效果:在將 Runnable 對象提交給 Executor 執行以前的操做,happen-before 被提交的 Runnable 的執行,也許在另外一個線程中。

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}
複製代碼

ExecutorService 接口

做爲一個 Executor,提供管理終止的方法和能夠生成 Future 以跟蹤一個或多個異步任務進度的方法。

ExecutorService 能夠被關閉,這將致使它拒絕接收新任務。ExecutorService 提供了兩種不一樣的方法來關閉。 shutdown 方法將容許先前提交的任務在終止以前保持執行,而 shutdownNow 方法阻止等待任務啓動並嘗試中止當前正在執行的任務。 終止時,executor 沒有正在執行的任務,沒有等待執行的任務,也沒有任何新任務能夠提交。 未使用的 ExecutorService 應被關閉以容許回收其資源。

方法 submit 經過建立並返回可用於取消執行和/或等待完成的 Future 來擴展基本方法 Executor.execute(Runnable)。 方法 invokeAny 和 invokeAll 執行最經常使用的批量執行形式,執行一組任務,而後等待至少一個或所有完成。類 ExecutorCompletionService 可用於編寫這些方法的自定義變體。

Executors 類爲此包中提供的 executor 服務提供工廠方法。

下面是網絡服務的草圖,其中線程池中的線程爲傳入的請求提供服務。 它使用預配置的 Executors.newFixedThreadPool 工廠方法:

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;

    public NetworkService(int port, int poolSize) throws IOException {
        serverSocket = new ServerSocket(port);
        pool = Executors.newFixedThreadPool(poolSize);
    }

    public void run() { // run the service
        try {
            for (; ; ) {
                pool.execute(new Handler(serverSocket.accept()));
            }
        } catch (IOException ex) {
            pool.shutdown();
        }
    }
}

class Handler implements Runnable {
    private final Socket socket;

    Handler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        // read and service request on socket
    }
}
複製代碼

如下方法分兩個階段關閉 ExecutorService,首先調用 shutdown 拒絕傳入的任務,而後在必要時調用 shutdownNow 以取消任何延遲的任務:

void shutdownAndAwaitTermination(ExecutorService pool) {
    pool.shutdown(); // Disable new tasks from being submitted
    try {
        // Wait a while for existing tasks to terminate
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow(); // Cancel currently executing tasks
            // Wait a while for tasks to respond to being cancelled
            if (!pool.awaitTermination(60, TimeUnit.SECONDS))
                System.err.println("Pool did not terminate");
        }
    } catch (InterruptedException ie) {
        // (Re-)Cancel if current thread also interrupted
        pool.shutdownNow();
        // Preserve interrupt status
        Thread.currentThread().interrupt();
    }
}
複製代碼

內存一致性效果:在將 Runnable 或 Callable 任務提交到 ExecutorService 以前,線程中的操做 happen-before 該任務所採起的任何行動,而該任務 happen-before 經過 Future.get() 檢索的結果。

// 請求關閉、發生超時或者當前線程中斷,不管哪個首先發生以後,都將致使阻塞,直到全部任務完成執行。
boolean awaitTermination(long timeout, TimeUnit unit)
// 執行給定的任務,當全部任務完成時,返回保持任務狀態和結果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
// 執行給定的任務,當全部任務完成或超時期滿時(不管哪一個首先發生),返回保持任務狀態和結果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
// 執行給定的任務,若是某個任務已成功完成(也就是未拋出異常),則返回其結果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
// 執行給定的任務,若是在給定的超時期滿前某個任務已成功完成(也就是未拋出異常),則返回其結果。
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
// 若是此執行程序已關閉,則返回 true。
boolean isShutdown()
// 若是關閉後全部任務都已完成,則返回 true。
boolean isTerminated()
// 啓動一次順序關閉,執行之前提交的任務,但不接受新任務。
void shutdown()
// 試圖中止全部正在執行的活動任務,暫停處理正在等待的任務,並返回等待執行的任務列表。
List<Runnable> shutdownNow()
// 提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future。
<T> Future<T> submit(Callable<T> task)
// 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。
Future<?> submit(Runnable task)
// 提交一個 Runnable 任務用於執行,並返回一個表示該任務的 Future。
<T> Future<T> submit(Runnable task, T result)
複製代碼

AbstractExecutorService 抽象類

提供 ExecutorService 執行方法的默認實現。 此類使用 newTaskFor 返回的 RunnableFuture 實現 submit, invokeAny 和 invokeAll 方法,默認爲此中提供的 FutureTask 類包。 例如, submit(Runnable) 的實現會建立一個執行並返回的關聯 RunnableFuture。 子類能夠覆蓋 newTaskFor 方法,以返回 FutureTask 之外的 RunnableFuture 實現。

擴展現例。 下面是一個類的草圖,它定製 ThreadPoolExecutor 以使用 CustomTask 類而不是默認的 FutureTask:

public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
    static class CustomTask<V> implements RunnableFuture<V> {...
    }

    protected <V> RunnableFuture<V> newTaskFor(Callable<V> c) {
        return new CustomTask<V>(c);
    }

    protected <V> RunnableFuture<V> newTaskFor(Runnable r, V v) {
        return new CustomTask<V>(r, v);
    }
    // ... add constructors, etc.
}
複製代碼
public abstract class AbstractExecutorService implements ExecutorService {

    /**
     * Returns a {@code RunnableFuture} for the given runnable and default
     * value.
     *
     * @param runnable the runnable task being wrapped
     * @param value the default value for the returned future
     * @param <T> the type of the given value
     * @return a {@code RunnableFuture} which, when run, will run the
     * underlying runnable and which, as a {@code Future}, will yield
     * the given value as its result and provide for cancellation of
     * the underlying task
     * @since 1.6
     */
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

    /**
     * Returns a {@code RunnableFuture} for the given callable task.
     *
     * @param callable the callable task being wrapped
     * @param <T> the type of the callable's result * @return a {@code RunnableFuture} which, when run, will call the * underlying callable and which, as a {@code Future}, will yield * the callable's result as its result and provide for
     * cancellation of the underlying task
     * @since 1.6
     */
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

    /**
     * the main mechanics of invokeAny.
     */
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (tasks == null)
            throw new NullPointerException();
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        ExecutorCompletionService<T> ecs =
            new ExecutorCompletionService<T>(this);

        // For efficiency, especially in executors with limited
        // parallelism, check to see if previously submitted tasks are
        // done before submitting more of them. This interleaving
        // plus the exception mechanics account for messiness of main
        // loop.

        try {
            // Record exceptions so that if we fail to obtain any
            // result, we can throw the last exception we got.
            ExecutionException ee = null;
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Iterator<? extends Callable<T>> it = tasks.iterator();

            // Start one task for sure; the rest incrementally
            futures.add(ecs.submit(it.next()));
            --ntasks;
            int active = 1;

            for (;;) {
                Future<T> f = ecs.poll();
                if (f == null) {
                    if (ntasks > 0) {
                        --ntasks;
                        futures.add(ecs.submit(it.next()));
                        ++active;
                    }
                    else if (active == 0)
                        break;
                    else if (timed) {
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                        if (f == null)
                            throw new TimeoutException();
                        nanos = deadline - System.nanoTime();
                    }
                    else
                        f = ecs.take();
                }
                if (f != null) {
                    --active;
                    try {
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }

            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
        }
    }

    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
        try {
            return doInvokeAny(tasks, false, 0);
        } catch (TimeoutException cannotHappen) {
            assert false;
            return null;
        }
    }

    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        return doInvokeAny(tasks, true, unit.toNanos(timeout));
    }

    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks) {
                RunnableFuture<T> f = newTaskFor(t);
                futures.add(f);
                execute(f);
            }
            for (int i = 0, size = futures.size(); i < size; i++) {
                Future<T> f = futures.get(i);
                if (!f.isDone()) {
                    try {
                        f.get();
                    } catch (CancellationException ignore) {
                    } catch (ExecutionException ignore) {
                    }
                }
            }
            done = true;
            return futures;
        } finally {
            if (!done)
                for (int i = 0, size = futures.size(); i < size; i++)
                    futures.get(i).cancel(true);
        }
    }

    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
        if (tasks == null)
            throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
        boolean done = false;
        try {
            for (Callable<T> t : tasks)
                futures.add(newTaskFor(t));

            final long deadline = System.nanoTime() + nanos;
            final int size = futures.size();

            // Interleave time checks and calls to execute in case
            // executor doesn't have any/much parallelism. for (int i = 0; i < size; i++) { execute((Runnable)futures.get(i)); nanos = deadline - System.nanoTime(); if (nanos <= 0L) return futures; } for (int i = 0; i < size; i++) { Future<T> f = futures.get(i); if (!f.isDone()) { if (nanos <= 0L) return futures; try { f.get(nanos, TimeUnit.NANOSECONDS); } catch (CancellationException ignore) { } catch (ExecutionException ignore) { } catch (TimeoutException toe) { return futures; } nanos = deadline - System.nanoTime(); } } done = true; return futures; } finally { if (!done) for (int i = 0, size = futures.size(); i < size; i++) futures.get(i).cancel(true); } } } 複製代碼

ThreadPoolExecutor 類

做爲一個 ExecutorService,它使用可能的幾個池化線程之一執行每一個提交的任務,一般使用 Executors 工廠方法配置。

線程池解決了兩個不一樣的問題:它們一般在執行大量異步任務時將提高性能,這是因爲減小了每一個任務的調用開銷,而且它們提供了一種綁定和管理資源和線程的方法,減小了執行一系列任務時所消耗的性能。 每一個 ThreadPoolExecutor 還維護一些基本統計信息,例如已完成任務的數量。

爲了在各類上下文中有用,該類提供了許多可調參數和可擴展性鉤子。 可是,程序員應該使用更方便的 Executors 工廠方法 Executor.newCachedThreadPool(無界線程池,自動線程回收), Executors.newFixedThreadPool(固定大小的線程池)和 Executors.newSingleThreadExecutor(單一後臺線程),爲最多見的使用場景預配置設置。 不然,在手動配置和調整此類時,請使用如下指南:

核心和最大池大小

ThreadPoolExecutor 將根據corePoolSize(getCorePoolSize)和maximumPoolSize(getMaximumPoolSize)設置的邊界自動調整線程池大小(getPoolSize)。

當在方法 execute(Runnable) 中提交新任務而且運行的線程少於corePoolSize時,即便其餘工做線程處於空閒狀態,也會建立一個新線程來處理該請求。 若是有多於corePoolSize但少於maximumPoolSize的運行線程,則只有在隊列已滿時纔會建立新線程。 經過設置corePoolSize和maximumPoolSize相同,能夠建立一個固定大小的線程池。 經過將maximumPoolSize設置爲基本無限制的值(例如 Integer.MAX_VALUE),能夠容許池容納任意數量的併發任務。 最典型的狀況是,核心和最大池大小僅在構造時設置,但也可使用 setCorePoolSize 和 #setMaximumPoolSize 動態更改。

按需構建

默認狀況下,即便核心線程最初只在新任務到達時建立並啓動,但可使用方法 prestartCoreThread 或 prestartAllCoreThreads 動態覆寫。 若是使用非空隊列構造池,則可能須要預啓動線程。

建立新線程

使用 ThreadFactory 建立新線程。 若是沒有另外指定,則使用 Executors.defaultThreadFactory,它建立的線程都在同一個 ThreadGroup 中,而且具備相同的 NORM_PRIORITY 優先級和非守護進程狀態。 經過提供不一樣的ThreadFactory,能夠更改線程的名稱,線程組,優先級,守護程序狀態等。若是 ThreadFactory 在經過從 newThread 返回null而沒法建立線程時,executor 將繼續,但可能沒法執行任何任務。 線程應該擁有「modifyThread」 RuntimePermission。 若是使用池的工做線程或其餘線程不具備此權限,則服務可能會降級:配置更改可能不會及時生效,而且關閉池可能保持能夠終止但未完成的狀態。

保持存活的時間

若是池當前具備多個corePoolSize線程,則多餘的線程若是空閒時間超過keepAliveTime,則將終止(getKeepAliveTime(TimeUnit))。 這提供了一種在不主動使用池時減小資源消耗的方法。 若是池稍後變得更活躍,則將構造新線程。 也可使用方法 setKeepAliveTime(long,TimeUnit) 動態更改此參數。 使用值 Long.MAX_VALUE TimeUnit#NANOSECONDS 能夠有效地禁止空閒線程在關閉以前終止。 默認狀況下,僅當存在多個corePoolSize線程時,保持活動策略才適用。 可是方法 allowCoreThreadTimeOut(boolean) 也可用於將此超時策略應用於核心線程,只要keepAliveTime值爲非零。

隊列

任何BlockingQueue均可以用於傳輸和保存所提交的任務。這個隊列的使用與池大小交互:

  1. 若是運行的線程少於corePoolSize的線程,Executor 老是添加新線程,而不是排隊。

  2. 若是corePoolSize或更多的線程正在運行,Executor 老是喜歡排隊請求,而不是添加一個新的線程。

  3. 若是請求不能排隊,那麼將建立一個新的線程,除非這個線程超過maximumPoolSize,在這種狀況下,該任務將被拒絕。

排隊的通常策略有三種:

直接切換。工做隊列的一個很好的默認選擇是 SynchronousQueue,它將任務移交給線程而不另外保存它們。 在這裏,若是沒有線程當即可用於運行它,則嘗試對任務進行入隊將失敗,所以將構造新線程。 此策略在處理可能具備內部依賴性的請求集時避免了鎖定。 直接切換一般須要無限制的maximumPoolSizes以免拒絕新提交的任務。 這反過來認可,當命令繼續以比處理它們更快的速度到達時,無限制的線程增加的可能性。

無界隊列。使用無界隊列(例如沒有預約義容量的 LinkedBlockingQueue 將致使新任務在全部corePoolSize線程忙時在隊列中等待。 所以,只會建立corePoolSize線程。 (maximumPoolSize的值所以沒有任何影響。)這多是適當的每一個任務徹底獨立於其餘任務,所以任務不會影響彼此的執行; 例如,在網頁服務器中。 雖然這種排隊方式能夠有助於平滑瞬態突發請求,但須要認可的是當命令繼續以平均到達的速度超過可處理速度時,可能致使無限制的工做隊列增加。

有界隊列。有界隊列(例如, ArrayBlockingQueue)與有限maximumPoolSizes一塊兒使用時有助於防止資源耗盡,但可能更難調整和控制。 隊列大小和最大池大小能夠相互交換:使用大型隊列和小型池最小化CPU使用率,OS資源和上下文切換開銷,但可能致使人爲的低吞吐量。 若是任務常常被阻塞(例如,若是它們是I / O綁定的),那麼系統可能會安排更多線程的時間,而不是容許的時間。 使用小隊列一般須要更大的池大小,這會使CPU更繁忙,但可能會遇到不可接受的調度開銷,這也會下降吞吐量。

被拒絕的任務

當 Executor 關閉時,以及當Executor對最大線程和工做隊列容量使用有限邊界時,方法 execute(Runnable) 中提交的新任務將被拒絕,由於已經飽和了。 在任何一種狀況下, execute 方法都會調用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(Runnable,ThreadPoolExecutor) 方法。 提供了四種預約義的處理程序策:

在默認的 ThreadPoolExecutor.AbortPolicy 中,處理程序在拒絕時拋出運行時 異常 RejectedExecutionException。

在 ThreadPoolExecutor.CallerRunsPolicy 中,調用 execute 自己的線程運行該任務。 這提供了一個簡單的反饋控制機制,能夠減慢提交新任務的速度。

在 ThreadPoolExecutor.DiscardPolicy 中,簡單地刪除了沒法執行的任務。

在 ThreadPoolExecutor.DiscardOldestPolicy 中,若是 executor 未關閉,則會刪除工做隊列頭部的任務,而後重試執行(可能會再次失敗,致使重複執行)。

能夠定義和使用其餘種類的 RejectedExecutionHandler 擴展類。 這樣作須要特別當心,特別是當策略設計爲僅在特定容量或排隊策略下工做時。

鉤子方法

此類提供在執行每一個任務以前和以後調用的 protected 可覆蓋 beforeExecute(Thread,Runnable) 和 afterExecute(Runnable,Throwable) 方法。 這些能夠用來操縱執行環境; 例如,從新初始化 ThreadLocals ,收集統計信息或添加日誌條目。 此外,能夠重寫方法 terminated 以執行 Executor 徹底終止後須要執行的任何特殊處理。

若是鉤子或回調方法拋出異常,則內部工做者線程可能會失敗並忽然終止。

隊列維護

方法 getQueue() 容許訪問工做隊列以進行監視和調試。 強烈建議不要將此方法用於任何其餘目的。 當大量排隊的任務被取消時,兩種提供的方法 remove(Runnable) 和 purge 可用於協助存儲回收。

終結

程序中再也不引用的池和沒有剩餘的線程將自動 shutdown。 若是但願確保即便用戶忘記調用 shutdown 也會回收未引用的池,那麼必須經過設置適當的保持活動時間,使用零核心線程的下限來安排未使用的線程最終死亡 和/或設置 allowCoreThreadTimeOut(boolean)。

擴展現例。 此類的大多數擴展都會覆蓋一個或多個受保護的鉤子方法。 例如,這是一個添加簡單暫停/恢復功能的子類:

class PausableThreadPoolExecutor extends ThreadPoolExecutor {
        private boolean isPaused;
        private ReentrantLock pauseLock = new ReentrantLock();
        private Condition unpaused = pauseLock.newCondition();

        public PausableThreadPoolExecutor(...) {
            super(...);
        }

        protected void beforeExecute(Thread t, Runnable r) {
            super.beforeExecute(t, r);
            pauseLock.lock();
            try {
                while (isPaused) unpaused.await();
            } catch (InterruptedException ie) {
                t.interrupt();
            } finally {
                pauseLock.unlock();
            }
        }

        public void pause() {
            pauseLock.lock();
            try {
                isPaused = true;
            } finally {
                pauseLock.unlock();
            }
        }

        public void resume() {
            pauseLock.lock();
            try {
                isPaused = false;
                unpaused.signalAll();
            } finally {
                pauseLock.unlock();
            }
        }
    }
複製代碼
// 用給定的初始參數和默認的線程工廠及被拒絕的執行處理程序建立新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
// 用給定的初始參數和默認的線程工廠建立新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
// 用給定的初始參數和默認被拒絕的執行處理程序建立新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
// 用給定的初始參數建立新的 ThreadPoolExecutor。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

// 基於完成執行給定 Runnable 所調用的方法。
protected void afterExecute(Runnable r, Throwable t)
// 若是在保持活動時間內沒有任務到達,新任務到達時正在替換(若是須要),則設置控制核心線程是超時仍是終止的策略。
void allowCoreThreadTimeOut(boolean value)
// 若是此池容許核心線程超時和終止,若是在 keepAlive 時間內沒有任務到達,新任務到達時正在替換(若是須要),則返回 true。
boolean allowsCoreThreadTimeOut()
// 請求關閉、發生超時或者當前線程中斷,不管哪個首先發生以後,都將致使阻塞,直到全部任務完成執行。
boolean awaitTermination(long timeout, TimeUnit unit)
// 在執行給定線程中的給定 Runnable 以前調用的方法。
protected void beforeExecute(Thread t, Runnable r)
// 在未來某個時間執行給定任務。
void execute(Runnable command)
// 當再也不引用此執行程序時,調用 shutdown。
protected void finalize()
// 返回主動執行任務的近似線程數。
int getActiveCount()
// 返回已完成執行的近似任務總數。
long getCompletedTaskCount()
// 返回核心線程數。
int getCorePoolSize()
// 返回線程保持活動的時間,該時間就是超過核心池大小的線程能夠在終止前保持空閒的時間值。
long getKeepAliveTime(TimeUnit unit)
// 返回曾經同時位於池中的最大線程數。
int getLargestPoolSize()
// 返回容許的最大線程數。
int getMaximumPoolSize()
// 返回池中的當前線程數。
int getPoolSize()
// 返回此執行程序使用的任務隊列。
BlockingQueue<Runnable> getQueue()
// 返回用於未執行任務的當前處理程序。
RejectedExecutionHandler getRejectedExecutionHandler()
// 返回曾計劃執行的近似任務總數。
long getTaskCount()
// 返回用於建立新線程的線程工廠。
ThreadFactory getThreadFactory()
// 若是此執行程序已關閉,則返回 true。
boolean isShutdown()
// 若是關閉後全部任務都已完成,則返回 true。
boolean isTerminated()
// 若是此執行程序處於在 shutdown 或 shutdownNow 以後正在終止但還沒有徹底終止的過程當中,則返回 true。
boolean isTerminating()
// 啓動全部核心線程,使其處於等待工做的空閒狀態。
int prestartAllCoreThreads()
// 啓動核心線程,使其處於等待工做的空閒狀態。
boolean prestartCoreThread()
// 嘗試從工做隊列移除全部已取消的 Future 任務。
void purge()
// 從執行程序的內部隊列中移除此任務(若是存在),從而若是還沒有開始,則其再也不運行。
boolean remove(Runnable task)
// 設置核心線程數。
void setCorePoolSize(int corePoolSize)
// 設置線程在終止前能夠保持空閒的時間限制。
void setKeepAliveTime(long time, TimeUnit unit)
// 設置容許的最大線程數。
void setMaximumPoolSize(int maximumPoolSize)
// 設置用於未執行任務的新處理程序。
void setRejectedExecutionHandler(RejectedExecutionHandler handler)
// 設置用於建立新線程的線程工廠。
void setThreadFactory(ThreadFactory threadFactory)
// 按過去執行已提交任務的順序發起一個有序的關閉,可是不接受新任務。
void shutdown()
// 嘗試中止全部的活動執行任務、暫停等待任務的處理,並返回等待執行的任務列表。
List<Runnable> shutdownNow()
// 當 Executor 已經終止時調用的方法。
protected void terminated()
複製代碼

ScheduledExecutorService 接口

一個 ExecutorService,它能夠調度命令在給定的延遲以後運行,或者按期執行。

schedule 方法建立具備各類延遲的任務,並返回可用於取消或檢查執行的任務對象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法建立並執行按期運行的任務,直到被取消。

使用 Executor.execute(Runnable) 和 ExecutorService.submit 方法提交的命令的調度請求延遲爲零。 schedule 方法中也容許零延遲和負延遲(但不包括時間段),並將其視爲當即執行的請求。

全部 schedule 方法都接受相對延遲和週期做爲參數,而不是絕對時間或日期。 將表示爲 java.util.Date 的絕對時間轉換爲所需的形式是一件簡單的事情。 例如,要在某個未來安排 date,可使用: schedule(task,date.getTime() - System.currentTimeMillis(),TimeUnit.MILLISECONDS) 。 但請注意,相對延遲的到期不必定與因爲網絡時間同步協議,時鐘漂移或其餘因素而啓用任務的當前 Date 一致。

Executors 類爲此程序包中提供的 ScheduledExecutorService 實現提供了方便的工廠方法。

這是一個帶有方法的類,該方法將 ScheduledExecutorService 設置爲每隔一小時發出十秒鐘的嗶聲:

import static java.util.concurrent.TimeUnit.*;
class BeeperControl {
    private final ScheduledExecutorService scheduler =
            Executors.newScheduledThreadPool(1);
     

    public void beepForAnHour() {
        final Runnable beeper = new Runnable() {
            public void run() {
                System.out.println("beep");
            }
        };
        final ScheduledFuture<?> beeperHandle =
                scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
        scheduler.schedule(new Runnable() {
            public void run() {
                beeperHandle.cancel(true);
            }
        }, 60 * 60, SECONDS);
    }
}
複製代碼
// 建立並執行在給定延遲後啓用的 ScheduledFuture。 
ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) 

// 建立並執行在給定延遲後啓用的一次性操做。 
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) 

// 建立並執行一個在給定初始延遲後首次啓用的按期操做,後續操做具備給定的週期;也就是將在 initialDelay 後開始執行,而後在 initialDelay+period 後執行,接着在 initialDelay + 2 * period 後執行,依此類推。 
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) 

// 建立並執行一個在給定初始延遲後首次啓用的按期操做,隨後,在每一次執行終止和下一次執行開始之間都存在給定的延遲。 
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
複製代碼

ScheduledThreadPoolExecutor

一個 ThreadPoolExecutor ,能夠額外安排命令在給定的延遲後運行,或按期執行。 當須要多個工做線程時,或者當須要 ThreadPoolExecutor(此類擴展)的額外靈活性或功能時,此類優於 java.util.Timer。

延遲任務在啓用後當即執行,但沒有任何實時保證啓用它們後什麼時候啓動它們。 按照先進先出(FIFO)提交順序啓用計劃徹底相同執行時間的任務。

在提交的任務在運行以前取消時,將禁止執行。 默認狀況下,此類已取消的任務不會自動從工做隊列中刪除,直到其延遲過去。 雖然這能夠進一步檢查和監控,但也可能致使取消任務的無限制保留。 爲避免這種狀況,請將 setRemoveOnCancelPolicy 設置爲 true ,這會致使在取消時當即從工做隊列中刪除任務。

經過 scheduleAtFixedRate 或 scheduleWithFixedDelay 安排的任務的連續執行不重疊。 雖然不一樣的執行能夠由不一樣的線程執行,但先前執行的效果 happen-before 以後的效果。

雖然這個類繼承自 ThreadPoolExecutor,可是一些繼承的調優方法對它沒用。 特別是,由於它使用 corePoolSize 線程和無界隊列充當固定大小的池,因此對 maximumPoolSize 的調整沒有任何有用的效果。 此外,將 corePoolSize 設置爲零或使用 allowCoreThreadTimeOut 幾乎毫不是一個好主意,由於一旦它們有資格運行,這可能會使池沒有線程來處理任務。

擴展註釋:此類覆蓋 ThreadPoolExecutor.execute(Runnable) execute 和 AbstractExecutorService.submit(Runnable) submit 方法,以生成內部 ScheduledFuture 對象以控制每一個任務的延遲和調度。 爲了保留功能,子類中這些方法的任何進一步覆蓋必須調用超類版本,這有效地禁用了其餘任務自定義。 可是,此類提供了替代的受保護擴展方法 decorateTask 支持 Runnable 和 Callable 各一個版本),可用於自定義用於執行經過 submit, schedule, scheduleAtFixedRate 和 scheduleWithFixedDelay。 默認狀況下, ScheduledThreadPoolExecutor 使用擴展 FutureTask 的任務類型。 可是,可使用如下形式的子類來修改或替換它:

public class CustomScheduledExecutor extends ScheduledThreadPoolExecutor {

    static class CustomTask<V> implements RunnableScheduledFuture<V> {
        ...
    }

    protected <V> RunnableScheduledFuture<V> decorateTask(
            Runnable r, RunnableScheduledFuture<V> task) {
        return new CustomTask<V>(r, task);
    }

    protected <V> RunnableScheduledFuture<V> decorateTask(
            Callable<V> c, RunnableScheduledFuture<V> task) {
        return new CustomTask<V>(c, task);
    }
    // ... add constructors, etc.
}
複製代碼

Executors 類

此程序包中定義的 Executor, ExecutorService,ScheduledExecutorService, ThreadFactory 和 Callable 類的工廠和實用程序方法。 該類支持如下幾種方法:

*   <li> Methods that create and return an {@link ExecutorService}
 *        set up with commonly useful configuration settings.
 *   <li> Methods that create and return a {@link ScheduledExecutorService}
 *        set up with commonly useful configuration settings.
 *   <li> Methods that create and return a "wrapped" ExecutorService, that
 *        disables reconfiguration by making implementation-specific methods
 *        inaccessible.
 *   <li> Methods that create and return a {@link ThreadFactory}
 *        that sets newly created threads to a known state.
 *   <li> Methods that create and return a {@link Callable}
 *        out of other closure-like forms, so they can be used
 *        in execution methods requiring {@code Callable}.
複製代碼
// 返回 Callable 對象,調用它時可運行給定特權的操做並返回其結果。
static Callable<Object> callable(PrivilegedAction<?> action)
// 返回 Callable 對象,調用它時可運行給定特權的異常操做並返回其結果。
static Callable<Object> callable(PrivilegedExceptionAction<?> action)
// 返回 Callable 對象,調用它時可運行給定的任務並返回 null。
static Callable<Object> callable(Runnable task)
// 返回 Callable 對象,調用它時可運行給定的任務並返回給定的結果。
static <T> Callable<T> callable(Runnable task, T result)
// 返回用於建立新線程的默認線程工廠。
static ThreadFactory defaultThreadFactory()
// 建立一個可根據須要建立新線程的線程池,可是在之前構造的線程可用時將重用它們。
static ExecutorService newCachedThreadPool()
// 建立一個可根據須要建立新線程的線程池,可是在之前構造的線程可用時將重用它們,並在須要時使用提供的 ThreadFactory 建立新線程。
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
// 建立一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程。
static ExecutorService newFixedThreadPool(int nThreads)
// 建立一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程,在須要時使用提供的 ThreadFactory 建立新線程。
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
// 建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
// 建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
// 建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
static ExecutorService newSingleThreadExecutor()
// 建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程,並在須要時使用提供的 ThreadFactory 建立新線程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
// 建立一個單線程執行程序,它可安排在給定延遲後運行命令或者按期地執行。
static ScheduledExecutorService newSingleThreadScheduledExecutor()
// 建立一個單線程執行程序,它可安排在給定延遲後運行命令或者按期地執行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
// 返回 Callable 對象,調用它時可在當前的訪問控制上下文中執行給定的 callable 對象。
static <T> Callable<T> privilegedCallable(Callable<T> callable)
// 返回 Callable 對象,調用它時可在當前的訪問控制上下文中,使用當前上下文類加載器做爲上下文類加載器來執行給定的 callable 對象。
static <T> Callable<T> privilegedCallableUsingCurrentClassLoader(Callable<T> callable)
// 返回用於建立新線程的線程工廠,這些新線程與當前線程具備相同的權限。
static ThreadFactory privilegedThreadFactory()
// 返回一個將全部已定義的 ExecutorService 方法委託給指定執行程序的對象,可是使用強制轉換可能沒法訪問其餘方法。
static ExecutorService unconfigurableExecutorService(ExecutorService executor)
// 返回一個將全部已定義的 ExecutorService 方法委託給指定執行程序的對象,可是使用強制轉換可能沒法訪問其餘方法。
static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor)
複製代碼

Android 線程池執行器

它是一個功能強大的任務執行框架,由於它支持隊列中的任務添加,任務取消和任務優先級。

它減小了與線程建立相關的開銷,由於它在其線程池中管理所需數量的線程。

線程池執行器有本身的建立線程的線程工廠。

public class PriorityThreadFactory implements ThreadFactory {

    private final int mThreadPriority;

    public PriorityThreadFactory(int threadPriority) {
        mThreadPriority = threadPriority;
    }

    @Override
    public Thread newThread(final Runnable runnable) {
        Runnable wrapperRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    Process.setThreadPriority(mThreadPriority);//設置優先級
                } catch (Throwable t) {

                }
                runnable.run();
            }
        };
        return new Thread(wrapperRunnable);
    }
}
複製代碼

主線程執行器

public class MainThreadExecutor implements Executor {

    private final Handler handler = new Handler(Looper.getMainLooper());

    @Override
    public void execute(Runnable runnable) {
        handler.post(runnable);
    }
}
複製代碼

將線程池執行器封裝在 DefaultExecutorSupplier 中,能夠包含多個線程池執行器。

/*
* Singleton class for default executor supplier
*/
public class DefaultExecutorSupplier{
    /*
    * Number of cores to decide the number of threads
    */
    public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    
    /*
    * thread pool executor for background tasks
    */
    private final ThreadPoolExecutor mForBackgroundTasks;
    /*
    * thread pool executor for light weight background tasks
    */
    private final ThreadPoolExecutor mForLightWeightBackgroundTasks;
    /*
    * thread pool executor for main thread tasks
    */
    private final Executor mMainThreadExecutor;
    /*
    * an instance of DefaultExecutorSupplier
    */
    private static DefaultExecutorSupplier sInstance;

    /*
    * returns the instance of DefaultExecutorSupplier
    */
    public static DefaultExecutorSupplier getInstance() {
       if (sInstance == null) {
         synchronized(DefaultExecutorSupplier.class){                                                                  
             sInstance = new DefaultExecutorSupplier();      
        }
        return sInstance;
    }

    /*
    * constructor for  DefaultExecutorSupplier
    */ 
    private DefaultExecutorSupplier() {
        
        // setting the thread factory
        ThreadFactory backgroundPriorityThreadFactory = new 
                PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND);
        
        // setting the thread pool executor for mForBackgroundTasks;
        mForBackgroundTasks = new ThreadPoolExecutor(
                NUMBER_OF_CORES * 2,
                NUMBER_OF_CORES * 2,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(),
                backgroundPriorityThreadFactory
        );
        
        // setting the thread pool executor for mForLightWeightBackgroundTasks;
        mForLightWeightBackgroundTasks = new ThreadPoolExecutor(
                NUMBER_OF_CORES * 2,
                NUMBER_OF_CORES * 2,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(),
                backgroundPriorityThreadFactory
        );
        
        // setting the thread pool executor for mMainThreadExecutor;
        mMainThreadExecutor = new MainThreadExecutor();
    }

    /*
    * returns the thread pool executor for background task
    */
    public ThreadPoolExecutor forBackgroundTasks() {
        return mForBackgroundTasks;
    }

    /*
    * returns the thread pool executor for light weight background task
    */
    public ThreadPoolExecutor forLightWeightBackgroundTasks() {
        return mForLightWeightBackgroundTasks;
    }

    /*
    * returns the thread pool executor for main thread task
    */
    public Executor forMainThreadTasks() {
        return mMainThreadExecutor;
    }
}
複製代碼

這樣子就可使用了

/*
* Using it for Background Tasks
*/
public void doSomeBackgroundWork(){
  DefaultExecutorSupplier.getInstance().forBackgroundTasks()
    .execute(new Runnable() {
    @Override
    public void run() {
       // do some background work here.
    }
  });
}

/*
* Using it for Light-Weight Background Tasks
*/
public void doSomeLightWeightBackgroundWork(){
  DefaultExecutorSupplier.getInstance().forLightWeightBackgroundTasks()
    .execute(new Runnable() {
    @Override
    public void run() {
       // do some light-weight background work here.
    }
  });
}

/*
* Using it for MainThread Tasks
*/
public void doSomeMainThreadWork(){
  DefaultExecutorSupplier.getInstance().forMainThreadTasks()
    .execute(new Runnable() {
    @Override
    public void run() {
       // do some Main Thread work here.
    }
  });
}
複製代碼

當前傳遞的任務都是 Runnable 類型,沒法得到返回值或者取消任務。可使用下列方式操做任務:

/*
* Get the future of the task by submitting it to the pool
*/
Future future = DefaultExecutorSupplier.getInstance().forBackgroundTasks().submit(new Runnable() {
    @Override
    public void run() {
      // do some background work here.
    }
});

/*
* cancelling the task
*/
future.cancel(true); 
複製代碼

添加能夠設置線程優先級的線程池處理器,首先設置優先級配置:

/**
 * Priority levels
 */
public enum Priority {
    /**
     * NOTE: DO NOT CHANGE ORDERING OF THOSE CONSTANTS UNDER ANY CIRCUMSTANCES.
     * Doing so will make ordering incorrect.
     */

    /**
     * Lowest priority level. Used for prefetches of data.
     */
    LOW,

    /**
     * Medium priority level. Used for warming of data that might soon get visible.
     */
    MEDIUM,

    /**
     * Highest priority level. Used for data that are currently visible on screen.
     */
    HIGH,

    /**
     * Highest priority level. Used for data that are required instantly(mainly for emergency).
     */
    IMMEDIATE;
}
複製代碼

建立具備優先級的任務實現

public class PriorityRunnable implements Runnable {

    private final Priority priority;

    public PriorityRunnable(Priority priority) {
        this.priority = priority;
    }

    @Override
    public void run() {
      // nothing to do here.
    }

    public Priority getPriority() {
        return priority;
    }
}
複製代碼

接着建立具備優先級的線程池處理器

public class PriorityThreadPoolExecutor extends ThreadPoolExecutor {

   public PriorityThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
         TimeUnit unit, ThreadFactory threadFactory) {
        //在調用父類方法時使用具備優先級的隊列
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit,new PriorityBlockingQueue<Runnable>(), threadFactory);
    }

    @Override
    public Future<?> submit(Runnable task) {
        //將普通的Runnable封裝成具備優先級的Runnable
        PriorityFutureTask futureTask = new PriorityFutureTask((PriorityRunnable) task);
        //進行入隊操做
        execute(futureTask);
        return futureTask;
    }

    private static final class PriorityFutureTask extends FutureTask<PriorityRunnable>
            implements Comparable<PriorityFutureTask> {
        private final PriorityRunnable priorityRunnable;

        public PriorityFutureTask(PriorityRunnable priorityRunnable) {
            super(priorityRunnable, null);
            this.priorityRunnable = priorityRunnable;
        }
        
        /*
         * compareTo() method is defined in interface java.lang.Comparable and it is used
         * to implement natural sorting on java classes. natural sorting means the the sort 
         * order which naturally applies on object e.g. lexical order for String, numeric 
         * order for Integer or Sorting employee by there ID etc. most of the java core 
         * classes including String and Integer implements CompareTo() method and provide
         * natural sorting.
         */
        @Override
        public int compareTo(PriorityFutureTask other) {
            Priority p1 = priorityRunnable.getPriority();
            Priority p2 = other.priorityRunnable.getPriority();
            return p2.ordinal() - p1.ordinal();
        }
    }
}
複製代碼

在 DefaultExecutorSupplier 中暴露出優先級線程池執行器

public class DefaultExecutorSupplier{

private final PriorityThreadPoolExecutor mForBackgroundTasks;

private DefaultExecutorSupplier() {
  
        mForBackgroundTasks = new PriorityThreadPoolExecutor(
                NUMBER_OF_CORES * 2,
                NUMBER_OF_CORES * 2,
                60L,
                TimeUnit.SECONDS,
                backgroundPriorityThreadFactory
        );

    }
}
複製代碼

使用方式爲

/*
* do some task at high priority
*/
public void doSomeTaskAtHighPriority(){
  DefaultExecutorSupplier.getInstance().forBackgroundTasks()
    .submit(new PriorityRunnable(Priority.HIGH) {
    @Override
    public void run() {
      // do some background work here at high priority.
    }
});
}
複製代碼

Android中的線程的優先級

在Android中有兩種線程的優先級,一種爲Android API版本,另外一種是 Java 原生版本。

Android API

THREAD_PRIORITY_DEFAULT,默認的線程優先級,值爲0。
THREAD_PRIORITY_LOWEST,最低的線程級別,值爲19。
THREAD_PRIORITY_BACKGROUND 後臺線程建議設置這個優先級,值爲10。
THREAD_PRIORITY_FOREGROUND 用戶正在交互的UI線程,代碼中沒法設置該優先級,系統會按照狀況調整到該優先級,值爲-2。
THREAD_PRIORITY_DISPLAY 也是與UI交互相關的優先級界別,可是要比THREAD_PRIORITY_FOREGROUND優先,代碼中沒法設置,由系統按照狀況調整,值爲-4。
THREAD_PRIORITY_URGENT_DISPLAY 顯示線程的最高級別,用來處理繪製畫面和檢索輸入事件,代碼中沒法設置成該優先級。值爲-8。
THREAD_PRIORITY_AUDIO 聲音線程的標準級別,代碼中沒法設置爲該優先級,值爲 -16。
THREAD_PRIORITY_URGENT_AUDIO 聲音線程的最高級別,優先程度較THREAD_PRIORITY_AUDIO要高。代碼中沒法設置爲該優先級。值爲-19。
THREAD_PRIORITY_MORE_FAVORABLE 相對THREAD_PRIORITY_DEFAULT稍微優先,值爲-1。
THREAD_PRIORITY_LESS_FAVORABLE 相對THREAD_PRIORITY_DEFAULT稍微落後一些,值爲1。
複製代碼

使用方式

new Thread () {
    @Override
    public void run() {
      super.run();
        android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    }
}.start();
複製代碼

Java API 的設置效果不如 Android API。

多線程編程中的三個核心概念

原子性

這一點,跟數據庫事務的原子性概念差很少,即一個操做(有可能包含有多個子操做)要麼所有執行(生效),要麼所有都不執行(都不生效)。

關於原子性,一個很是經典的例子就是銀行轉帳問題:好比A和B同時向C轉帳10萬元。若是轉帳操做不具備原子性,A在向C轉帳時,讀取了C的餘額爲20萬,而後加上轉帳的10萬,計算出此時應該有30萬,但還將來及將30萬寫回C的帳戶,此時B的轉帳請求過來了,B發現C的餘額爲20萬,而後將其加10萬並寫回。而後A的轉帳操做繼續——將30萬寫回C的餘額。這種狀況下C的最終餘額爲30萬,而非預期的40萬。

可見性

可見性是指,當多個線程併發訪問共享變量時,一個線程對共享變量的修改,其它線程可以當即看到。可見性問題是好多人忽略或者理解錯誤的一點。

CPU從主內存中讀數據的效率相對來講不高,如今主流的計算機中,都有幾級緩存。每一個線程讀取共享變量時,都會將該變量加載進其對應CPU的高速緩存裏,修改該變量後,CPU會當即更新該緩存,但並不必定會當即將其寫回主內存(實際上寫回主內存的時間不可預期)。此時其它線程(尤爲是不在同一個CPU上執行的線程)訪問該變量時,從主內存中讀到的就是舊的數據,而非第一個線程更新後的數據。

這一點是操做系統或者說是硬件層面的機制,因此不少應用開發人員常常會忽略。

順序性

順序性指的是,程序執行的順序按照代碼的前後順序執行。

如下面這段代碼爲例

boolean started = false; // 語句1
long counter = 0L; // 語句2
counter = 1; // 語句3
started = true; // 語句4
複製代碼

從代碼順序上看,上面四條語句應該依次執行,但實際上JVM真正在執行這段代碼時,並不保證它們必定徹底按照此順序執行。

處理器爲了提升程序總體的執行效率,可能會對代碼進行優化,其中的一項優化方式就是調整代碼順序,按照更高效的順序執行代碼。但它會保證程序最終的執行結果和代碼順序執行時的結果一致。

Java如何保證原子性

鎖和同步

經常使用的保證Java操做原子性的工具是鎖和同步方法(或者同步代碼塊)。使用鎖,能夠保證同一時間只有一個線程能拿到鎖,也就保證了同一時間只有一個線程能執行申請鎖和釋放鎖之間的代碼。

public void testLock () {
  lock.lock();
  try{
    int j = i;
    i = j + 1;
  } finally {
    lock.unlock();
  }
}
複製代碼

與鎖相似的是同步方法或者同步代碼塊。使用非靜態同步方法時,鎖住的是當前實例;使用靜態同步方法時,鎖住的是該類的Class對象;使用靜態代碼塊時,鎖住的是synchronized關鍵字後面括號內的對象。下面是同步代碼塊示例

public void testLock () {
  synchronized (anyObject){
    int j = i;
    i = j + 1;
  }
}
複製代碼

不管使用鎖仍是synchronized,本質都是同樣,經過鎖來實現資源的排它性,從而實際目標代碼段同一時間只會被一個線程執行,進而保證了目標代碼段的原子性。這是一種以犧牲性能爲代價的方法。

CAS(compare and swap)

基礎類型變量自增(i++)是一種常被新手誤覺得是原子操做而實際不是的操做。Java中提供了對應的原子操做類來實現該操做,並保證原子性,其本質是利用了CPU級別的CAS指令。因爲是CPU級別的指令,其開銷比須要操做系統參與的鎖的開銷小。AtomicInteger使用方法以下。

AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
  new Thread(() -> {
    for(int a = 0; a < iteration; a++) {
      atomicInteger.incrementAndGet();
    }
  }).start();
}
複製代碼

Java如何保證可見性

Java提供了volatile關鍵字來保證可見性。當使用volatile修飾某個變量時,它會保證對該變量的修改會當即被更新到內存中,而且將其它緩存中對該變量的緩存設置成無效,所以其它線程須要讀取該值時必須從主內存中讀取,從而獲得最新的值。

Java如何保證順序性

編譯器和處理器對指令進行從新排序時,會保證從新排序後的執行結果和代碼順序執行的結果一致,因此從新排序過程並不會影響單線程程序的執行,卻可能影響多線程程序併發執行的正確性。

Java中可經過volatile在必定程序上保證順序性,另外還能夠經過synchronized和鎖來保證順序性。

synchronized和鎖保證順序性的原理和保證原子性同樣,都是經過保證同一時間只會有一個線程執行目標代碼段來實現的。

除了從應用層面保證目標代碼段執行的順序性外,JVM還經過被稱爲happens-before原則隱式地保證順序性。兩個操做的執行順序只要能夠經過happens-before推導出來,則JVM會保證其順序性,反之JVM對其順序性不做任何保證,可對其進行任意必要的從新排序以獲取高效率。

happens-before原則(先行發生原則)

  • 傳遞規則:若是操做1在操做2前面,而操做2在操做3前面,則操做1確定會在操做3前發生。該規則說明了happens-before原則具備傳遞性
  • 鎖定規則:一個unlock操做確定會在後面對同一個鎖的lock操做前發生。這個很好理解,鎖只有被釋放了纔會被再次獲取
  • volatile變量規則:對一個被volatile修飾的寫操做先發生於後面對該變量的讀操做
  • 程序次序規則:一個線程內,按照代碼順序執行
  • 線程啓動規則:Thread對象的start()方法先發生於此線程的其它動做
  • 線程終結原則:線程的終止檢測後發生於線程中其它的全部操做
  • 線程中斷規則: 對線程interrupt()方法的調用先發生於對該中斷異常的獲取
  • 對象終結規則:一個對象構造先於它的finalize發生

volatile適用場景

volatile適用於不須要保證原子性,但卻須要保證可見性的場景。一種典型的使用場景是用它修飾用於中止線程的狀態標記。以下所示

boolean isRunning = false;
public void start () {
  new Thread( () -> {
    while(isRunning) {
      someOperation();
    }
  }).start();
}
public void stop () {
  isRunning = false;
}
複製代碼

在這種實現方式下,即便其它線程經過調用stop()方法將isRunning設置爲false,循環也不必定會當即結束。能夠經過volatile關鍵字,保證while循環及時獲得isRunning最新的狀態從而及時中止循環,結束線程。

線程安全Q&A

問:平時項目中使用鎖和synchronized比較多,而不多使用volatile,難道就沒有保證可見性? 答:鎖和synchronized便可以保證原子性,也能夠保證可見性。都是經過保證同一時間只有一個線程執行目標代碼段來實現的。

問:鎖和synchronized爲什麼能保證可見性? 答:根據JDK 7的Java doc中對concurrent包的說明,一個線程的寫結果保證對另外線程的讀操做可見,只要該寫操做能夠由happen-before原則推斷出在讀操做以前發生。

問:既然鎖和synchronized便可保證原子性也可保證可見性,爲什麼還須要volatile? 答:synchronized和鎖須要經過操做系統來仲裁誰得到鎖,開銷比較高,而volatile開銷小不少。所以在只須要保證可見性的條件下,使用volatile的性能要比使用鎖和synchronized高得多。

問:既然鎖和synchronized能夠保證原子性,爲何還須要AtomicInteger這種的類來保證原子操做? 答:鎖和synchronized須要經過操做系統來仲裁誰得到鎖,開銷比較高,而AtomicInteger是經過CPU級的CAS操做來保證原子性,開銷比較小。因此使用AtomicInteger的目的仍是爲了提升性能。

問:還有沒有別的辦法保證線程安全 答:有。儘量避免引發非線程安全的條件——共享變量。若是能從設計上避免共享變量的使用,便可避免非線程安全的發生,也就無須經過鎖或者synchronized以及volatile解決原子性、可見性和順序性的問題。

問:synchronized便可修飾非靜態方式,也可修飾靜態方法,還可修飾代碼塊,有何區別 答:synchronized修飾非靜態同步方法時,鎖住的是當前實例;synchronized修飾靜態同步方法時,鎖住的是該類的Class對象;synchronized修飾靜態代碼塊時,鎖住的是synchronized關鍵字後面括號內的對象。

sleep和wait到底什麼區別

其實這個問題應該這麼問——sleep和wait有什麼相同點。由於這兩個方法除了都能讓當前線程暫停執行完,幾乎沒有其它相同點。

wait方法是Object類的方法,這意味着全部的Java類均可以調用該方法。sleep方法是Thread類的靜態方法。

wait是在當前線程持有wait對象鎖的狀況下,暫時放棄鎖,並讓出CPU資源,並積極等待其它線程調用同一對象的notify或者notifyAll方法。注意,即便只有一個線程在等待,而且有其它線程調用了notify或者notifyAll方法,等待的線程只是被激活,可是它必須得再次得到鎖才能繼續往下執行。換言之,即便notify被調用,但只要鎖沒有被釋放,原等待線程由於未得到鎖仍然沒法繼續執行。

public class Wait {
  public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
      synchronized (Wait.class) {
        try {
          System.out.println(new Date() + " Thread1 is running");
          Wait.class.wait();
          System.out.println(new Date() + " Thread1 ended");
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    });
    thread1.start();
    
    Thread thread2 = new Thread(() -> {
      synchronized (Wait.class) {
        try {
          System.out.println(new Date() + " Thread2 is running");
          Wait.class.notify();
          // Don't use sleep method to avoid confusing for(long i = 0; i < 200000; i++) { for(long j = 0; j < 100000; j++) {} } System.out.println(new Date() + " Thread2 release lock"); } catch (Exception ex) { ex.printStackTrace(); } } for(long i = 0; i < 200000; i++) { for(long j = 0; j < 100000; j++) {} } System.out.println(new Date() + " Thread2 ended"); }); // Don't use sleep method to avoid confusing
    for(long i = 0; i < 200000; i++) {
      for(long j = 0; j < 100000; j++) {}
    }
    thread2.start();
  }
}
複製代碼

執行結果以下

Tue Jun 14 22:51:11 CST 2016 Thread1 is running
Tue Jun 14 22:51:23 CST 2016 Thread2 is running
Tue Jun 14 22:51:36 CST 2016 Thread2 release lock
Tue Jun 14 22:51:36 CST 2016 Thread1 ended
Tue Jun 14 22:51:49 CST 2016 Thread2 ended
複製代碼

從運行結果能夠看出

  • thread1執行wait後,暫停執行
  • thread2執行notify後,thread1並無繼續執行,由於此時thread2還沒有釋放鎖,thread1由於得不到鎖而不能繼續執行
  • thread2執行完synchronized語句塊後釋放鎖,thread1獲得通知並得到鎖,進而繼續執行

注意:wait方法須要釋放鎖,前提條件是它已經持有鎖。因此wait和notify(或者notifyAll)方法都必須被包裹在synchronized語句塊中,而且synchronized後鎖的對象應該與調用wait方法的對象同樣。不然拋出IllegalMonitorStateException

sleep方法告訴操做系統至少指定時間內不需爲線程調度器爲該線程分配執行時間片,並不釋放鎖(若是當前已經持有鎖)。實際上,調用sleep方法時並不要求持有任何鎖。

public class Sleep {
  public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
      synchronized (Sleep.class) {
        try {
          System.out.println(new Date() + " Thread1 is running");
          Thread.sleep(2000);
          System.out.println(new Date() + " Thread1 ended");
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    });
    thread1.start();
    
    Thread thread2 = new Thread(() -> {
      synchronized (Sleep.class) {
        try {
          System.out.println(new Date() + " Thread2 is running");
          Thread.sleep(2000);
          System.out.println(new Date() + " Thread2 ended");
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
      
      for(long i = 0; i < 200000; i++) {
        for(long j = 0; j < 100000; j++) {}
      }
    });
    
    // Don't use sleep method to avoid confusing for(long i = 0; i < 200000; i++) { for(long j = 0; j < 100000; j++) {} } thread2.start(); } } 複製代碼

執行結果以下

Thu Jun 16 19:46:06 CST 2016 Thread1 is running
Thu Jun 16 19:46:08 CST 2016 Thread1 ended
Thu Jun 16 19:46:13 CST 2016 Thread2 is running
Thu Jun 16 19:46:15 CST 2016 Thread2 ended
複製代碼

因爲thread 1和thread 2的run方法實現都在同步塊中,不管哪一個線程先拿到鎖,執行sleep時並不釋放鎖,所以其它線程沒法執行。直到前面的線程sleep結束並退出同步塊(釋放鎖),另外一個線程才獲得鎖並執行。

注意:sleep方法並不須要持有任何形式的鎖,也就不須要包裹在synchronized中。

調用sleep方法的線程,在jstack中顯示的狀態爲sleeping。

java.lang.Thread.State: TIMED_WAITING (sleeping)
複製代碼

調用wait方法的線程,在jstack中顯示的狀態爲on object monitor

java.lang.Thread.State: WAITING (on object monitor)
複製代碼

synchronized幾種用法

每一個Java對象均可以用作一個實現同步的互斥鎖,這些鎖被稱爲內置鎖。線程進入同步代碼塊或方法時自動得到內置鎖,退出同步代碼塊或方法時自動釋放該內置鎖。進入同步代碼塊或者同步方法是得到內置鎖的惟一途徑。

實例同步方法

synchronized用於修飾實例方法(非靜態方法)時,執行該方法須要得到的是該類實例對象的內置鎖(同一個類的不一樣實例擁有不一樣的內置鎖)。若是多個實例方法都被synchronized修飾,則當多個線程調用同一實例的不一樣同步方法(或者同一方法)時,須要競爭鎖。但當調用的是不一樣實例的方法時,並不須要競爭鎖。

靜態同步方法

synchronized用於修飾靜態方法時,執行該方法須要得到的是該類的class對象的內置鎖(一個類只有惟一一個class對象)。調用同一個類的不一樣靜態同步方法時會產生鎖競爭。

同步代碼塊

synchronized用於修飾代碼塊時,進入同步代碼塊須要得到synchronized關鍵字後面括號內的對象(能夠是實例對象也能夠是class對象)的內置鎖。

synchronized使用總結

鎖的使用是爲了操做臨界資源的正確性,而每每一個方法中並不是全部的代碼都操做臨界資源。換句話說,方法中的代碼每每並不都須要同步。此時建議不使用同步方法,而使用同步代碼塊,只對操做臨界資源的代碼,也即須要同步的代碼加鎖。這樣作的好處是,當一個線程在執行同步代碼塊時,其它線程仍然能夠執行該方法內同步代碼塊之外的部分,充分發揮多線程併發的優點,從而相較於同步整個方法而言提高性能。

釋放Java內置鎖的惟一方式是synchronized方法或者代碼塊執行結束。若某一線程在synchronized方法或代碼塊內發生死鎖,則對應的內置鎖沒法釋放,其它線程也沒法獲取該內置鎖(即進入跟該內置鎖相關的synchronized方法或者代碼塊)。

使用jstack dump線程棧時,可查看到相關線程經過synchronized獲取到或等待的對象,但Locked ownable synchronizers仍然顯示爲None。下例中,線程thead-test-b已獲取到類型爲java.lang.Double的對象的內置鎖(monitor),且該對象的內存地址爲0x000000076ab95cb8

"thread-test-b" #11 prio=5 os_prio=31 tid=0x00007fab0190b800 nid=0x5903 runnable [0x0000700010249000]
   java.lang.Thread.State: RUNNABLE
        at com.jasongj.demo.TestJstack.lambda$1(TestJstack.java:27)
        - locked <0x000000076ab95cb8> (a java.lang.Double)
        at com.jasongj.demo.TestJstack$$Lambda$2/1406718218.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
   Locked ownable synchronizers:
        - None
複製代碼

Java中的鎖

重入鎖

Java中的重入鎖(即ReentrantLock)與Java內置鎖同樣,是一種排它鎖。使用synchronized的地方必定能夠用ReentrantLock代替。

重入鎖須要顯示請求獲取鎖,並顯示釋放鎖。爲了不得到鎖後,沒有釋放鎖,而形成其它線程沒法得到鎖而形成死鎖,通常建議將釋放鎖操做放在finally塊裏,以下所示。

try{
  renentrantLock.lock();
  // 用戶操做
} finally {
  renentrantLock.unlock();
}
複製代碼

若是重入鎖已經被其它線程持有,則當前線程的lock操做會被阻塞。除了lock()方法以外,重入鎖(或者說鎖接口)還提供了其它獲取鎖的方法以實現不一樣的效果。

  • lockInterruptibly() 該方法嘗試獲取鎖,若獲取成功當即返回;若獲取不成功則阻塞等待。與lock方法不一樣的是,在阻塞期間,若是當前線程被打斷(interrupt)則該方法拋出InterruptedException。該方法提供了一種解除死鎖的途徑。
  • tryLock() 該方法試圖獲取鎖,若該鎖當前可用,則該方法當即得到鎖並當即返回true;若鎖當前不可用,則當即返回false。該方法不會阻塞,並提供給用戶對於成功獲利鎖與獲取鎖失敗進行不一樣操做的可能性。
  • tryLock(long time, TimeUnit unit) 該方法試圖得到鎖,若該鎖當前可用,則當即得到鎖並當即返回true。若鎖當前不可用,則等待相應的時間(由該方法的兩個參數決定):1)若該時間內鎖可用,則得到鎖,並返回true;2)若等待期間當前線程被打斷,則拋出InterruptedException;3)若等待時間結束仍未得到鎖,則返回false。

重入鎖可定義爲公平鎖或非公平鎖,默認實現爲非公平鎖。

  • 公平鎖是指多個線程獲取鎖被阻塞的狀況下,鎖變爲可用時,最新申請鎖的線程得到鎖。可經過在重入鎖(RenentrantLock)的構造方法中傳入true構建公平鎖,如Lock lock = new RenentrantLock(true)
  • 非公平鎖是指多個線程等待鎖的狀況下,鎖變爲可用狀態時,哪一個線程得到鎖是隨機的。synchonized至關於非公平鎖。可經過在重入鎖的構造方法中傳入false或者使用無參構造方法構建非公平鎖。

使用jstack dump線程棧時,可查看到獲取到或正在等待的鎖對象,獲取到該鎖的線程會在Locked ownable synchronizers處顯示該鎖的對象類型及內存地址。在下例中,從Locked ownable synchronizers部分可看到,線程thread-test-e獲取到公平重入鎖,且該鎖對象的內存地址爲0x000000076ae3d708

"thread-test-e" #17 prio=5 os_prio=31 tid=0x00007fefaa0b6800 nid=0x6403 runnable [0x0000700002939000]
   java.lang.Thread.State: RUNNABLE
        at com.jasongj.demo.TestJstack.lambda$4(TestJstack.java:64)
        at com.jasongj.demo.TestJstack$$Lambda$5/466002798.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
   Locked ownable synchronizers:
        - <0x000000076af86810> (a java.util.concurrent.locks.ReentrantLock$FairSync)
複製代碼

而線程thread-test-f因爲未獲取到鎖,而處於WAITING(parking)狀態,且它等待的鎖正是上文線程thread-test-e獲取的鎖(內存地址0x000000076af86810)

"thread-test-f" #18 prio=5 os_prio=31 tid=0x00007fefaa9b2800 nid=0x6603 waiting on condition [0x0000700002a3c000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076af86810> (a java.util.concurrent.locks.ReentrantLock$FairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:224)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at com.jasongj.demo.TestJstack.lambda$5(TestJstack.java:69)
        at com.jasongj.demo.TestJstack$$Lambda$6/33524623.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:745)
   Locked ownable synchronizers:
        - None
複製代碼

讀寫鎖

鎖能夠保證原子性和可見性。而原子性更可能是針對寫操做而言。對於讀多寫少的場景,一個讀操做無須阻塞其它讀操做,只須要保證讀和寫或者寫與寫不一樣時發生便可。此時,若是使用重入鎖(即排它鎖),對性能影響較大。Java中的讀寫鎖(ReadWriteLock)就是爲這種讀多寫少的場景而創造的。

實際上,ReadWriteLock接口並不是繼承自Lock接口,ReentrantReadWriteLock也只實現了ReadWriteLock接口而未實現Lock接口。ReadLock和WriteLock,是ReentrantReadWriteLock類的靜態內部類,它們實現了Lock接口。

一個ReentrantReadWriteLock實例包含一個ReentrantReadWriteLock.ReadLock實例和一個ReentrantReadWriteLock.WriteLock實例。經過readLock()和writeLock()方法可分別得到讀鎖實例和寫鎖實例,並經過Lock接口提供的獲取鎖方法得到對應的鎖。

讀寫鎖的鎖定規則以下:

  • 得到讀鎖後,其它線程可得到讀鎖而不能獲取寫鎖
  • 得到寫鎖後,其它線程既不能得到讀鎖也不能得到寫鎖
public class ReadWriteLockDemo {
  public static void main(String[] args) {
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    new Thread(() -> {
      readWriteLock.readLock().lock();
      try {
        System.out.println(new Date() + "\tThread 1 started with read lock");
        try {
          Thread.sleep(2000);
        } catch (Exception ex) {
        }
        System.out.println(new Date() + "\tThread 1 ended");
      } finally {
        readWriteLock.readLock().unlock();
      }
    }).start();
    new Thread(() -> {
      readWriteLock.readLock().lock();
      try {
        System.out.println(new Date() + "\tThread 2 started with read lock");
        try {
          Thread.sleep(2000);
        } catch (Exception ex) {
        }
        System.out.println(new Date() + "\tThread 2 ended");
      } finally {
        readWriteLock.readLock().unlock();
      }
    }).start();
    new Thread(() -> {
      Lock lock = readWriteLock.writeLock();
      lock.lock();
      try {
        System.out.println(new Date() + "\tThread 3 started with write lock");
        try {
          Thread.sleep(2000);
        } catch (Exception ex) {
          ex.printStackTrace();
        }
        System.out.println(new Date() + "\tThread 3 ended");
      } finally {
        lock.unlock();
      }
    }).start();
  }
}
複製代碼

執行結果以下

Sat Jun 18 21:33:46 CST 2016  Thread 1 started with read lock
Sat Jun 18 21:33:46 CST 2016  Thread 2 started with read lock
Sat Jun 18 21:33:48 CST 2016  Thread 2 ended
Sat Jun 18 21:33:48 CST 2016  Thread 1 ended
Sat Jun 18 21:33:48 CST 2016  Thread 3 started with write lock
Sat Jun 18 21:33:50 CST 2016  Thread 3 ended
複製代碼

從上面的執行結果可見,thread 1和thread 2都只需得到讀鎖,所以它們能夠並行執行。而thread 3由於須要獲取寫鎖,必須等到thread 1和thread 2釋放鎖後才能得到鎖。

條件鎖

條件鎖只是一個幫助用戶理解的概念,實際上並無條件鎖這種鎖。對於每一個重入鎖,均可以經過newCondition()方法綁定若干個條件對象。

條件對象提供如下方法以實現不一樣的等待語義

  • await() 調用該方法的前提是,當前線程已經成功得到與該條件對象綁定的重入鎖,不然調用該方法時會拋出IllegalMonitorStateException。調用該方法外,當前線程會釋放當前已經得到的鎖(這一點與上文講述的Java內置鎖的wait方法一致),而且等待其它線程調用該條件對象的signal()或者signalAll()方法(這一點與Java內置鎖wait後等待notify()或notifyAll()很像)。或者在等待期間,當前線程被打斷,則wait()方法會拋出InterruptedException並清除當前線程的打斷狀態。
  • await(long time, TimeUnit unit) 適用條件和行爲與await()基本一致,惟一不一樣點在於,指定時間以內沒有收到signal()或signalALL()信號或者線程中斷時該方法會返回false;其它狀況返回true。
  • awaitNanos(long nanosTimeout) 調用該方法的前提是,當前線程已經成功得到與該條件對象綁定的重入鎖,不然調用該方法時會拋出IllegalMonitorStateException。nanosTimeout指定該方法等待信號的的最大時間(單位爲納秒)。若指定時間內收到signal()或signalALL()則返回nanosTimeout減去已經等待的時間;若指定時間內有其它線程中斷該線程,則拋出InterruptedException並清除當前線程的打斷狀態;若指定時間內未收到通知,則返回0或負數。
  • awaitUninterruptibly() 調用該方法的前提是,當前線程已經成功得到與該條件對象綁定的重入鎖,不然調用該方法時會拋出IllegalMonitorStateException。調用該方法後,結束等待的惟一方法是其它線程調用該條件對象的signal()或signalALL()方法。等待過程當中若是當前線程被中斷,該方法仍然會繼續等待,同時保留該線程的中斷狀態。
  • awaitUntil(Date deadline) 適用條件與行爲與awaitNanos(long nanosTimeout)徹底同樣,惟一不一樣點在於它不是等待指定時間,而是等待由參數指定的某一時刻。

調用條件等待的注意事項

  • 調用上述任意條件等待方法的前提都是當前線程已經得到與該條件對象對應的重入鎖。
  • 調用條件等待後,當前線程讓出CPU資源。
  • 上述等待方法結束後,方法返回的前提是它能從新得到與該條件對象對應的重入鎖。若是沒法得到鎖,仍然會繼續等待。這也是awaitNanos(long nanosTimeout)可能會返回負值的緣由。
  • 一旦條件等待方法返回,則當前線程確定已經得到了對應的重入鎖。
  • 重入鎖能夠建立若干個條件對象,signal()和signalAll()方法只能喚醒相同條件對象的等待。
  • 一個重入鎖上能夠生成多個條件變量,不一樣線程能夠等待不一樣的條件,從而實現更加細粒度的的線程間通訊。

signal()與signalAll()

  • signal() 如有一個或若干個線程在等待該條件變量,則該方法會喚醒其中的一個(具體哪個,沒法預測)。調用該方法的前提是當前線程持有該條件變量對應的鎖,不然拋出IllegalMonitorStateException。
  • signalALL() 如有一個或若干個線程在等待該條件變量,則該方法會喚醒全部等待。調用該方法的前提是當前線程持有該條件變量對應的鎖,不然拋出IllegalMonitorStateException。
public class ConditionTest {
  public static void main(String[] args) throws InterruptedException {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    new Thread(() -> {
      lock.lock();
      try {
        System.out.println(new Date() + "\tThread 1 is waiting");
        try {
          long waitTime = condition.awaitNanos(TimeUnit.SECONDS.toNanos(2));
          System.out.println(new Date() + "\tThread 1 remaining time " + waitTime);
        } catch (Exception ex) {
          ex.printStackTrace();
        }
        System.out.println(new Date() + "\tThread 1 is waken up");
      } finally {
        lock.unlock();
      }
    }).start();
    
    new Thread(() -> {
      lock.lock();
      try{
        System.out.println(new Date() + "\tThread 2 is running");
        try {
          Thread.sleep(4000);
        } catch (Exception ex) {
          ex.printStackTrace();
        }
        condition.signal();
        System.out.println(new Date() + "\tThread 2 ended");
      } finally {
        lock.unlock();
      }
    }).start();
  }
}
複製代碼

執行結果以下

Sun Jun 19 15:59:09 CST 2016  Thread 1 is waiting
Sun Jun 19 15:59:09 CST 2016  Thread 2 is running
Sun Jun 19 15:59:13 CST 2016  Thread 2 ended
Sun Jun 19 15:59:13 CST 2016  Thread 1 remaining time -2003467560
Sun Jun 19 15:59:13 CST 2016  Thread 1 is waken up
複製代碼

從執行結果能夠看出,雖然thread 2一開始就調用了signal()方法去喚醒thread 1,可是由於thread 2在4秒鐘後才釋放鎖,也即thread 1在4秒後纔得到鎖,因此thread 1的await方法在4秒鐘後才返回,而且返回負值。

相關文章
相關標籤/搜索