其實這個問題應該這麼問——sleep和wait有什麼相同點。由於這兩個方法除了都能讓當前線程暫停執行完,幾乎沒有其它相同點。java
wait方法是Object類的方法,這意味着全部的Java類均可以調用該方法。sleep方法是Thread類的靜態方法。安全
wait是在當前線程持有wait對象鎖的狀況下,暫時放棄鎖,並讓出CPU資源,並積極等待其它線程調用同一對象的notify或者notifyAll方法。注意,即便只有一個線程在等待,而且有其它線程調用了notify或者notifyAll方法,等待的線程只是被激活,可是它必須得再次得到鎖才能繼續往下執行。換言之,即便notify被調用,但只要鎖沒有被釋放,原等待線程由於未得到鎖仍然沒法繼續執行。測試代碼以下所示bash
import java.util.Date; 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
從運行結果能夠看出併發
注意:wait方法須要釋放鎖,前提條件是它已經持有鎖。因此wait和notify(或者notifyAll)方法都必須被包裹在synchronized語句塊中,而且synchronized後鎖的對象應該與調用wait方法的對象同樣。不然拋出IllegalMonitorStateException性能
sleep方法告訴操做系統至少指定時間內不需爲線程調度器爲該線程分配執行時間片,並不釋放鎖(若是當前已經持有鎖)。實際上,調用sleep方法時並不要求持有任何鎖。測試
package com.test.thread; import java.util.Date; 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(); } }
執行結果以下ui
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結束並退出同步塊(釋放鎖),另外一個線程才獲得鎖並執行。spa
注意:sleep方法並不須要持有任何形式的鎖,也就不須要包裹在synchronized中。操作系統
每一個Java對象均可以用作一個實現同步的互斥鎖,這些鎖被稱爲內置鎖。線程進入同步代碼塊或方法時自動得到內置鎖,退出同步代碼塊或方法時自動釋放該內置鎖。進入同步代碼塊或者同步方法是得到內置鎖的惟一途徑。
synchronized用於修飾實例方法(非靜態方法)時,執行該方法須要得到的是該類實例對象的內置鎖(同一個類的不一樣實例擁有不一樣的內置鎖)。若是多個實例方法都被synchronized修飾,則當多個線程調用同一實例的不一樣同步方法(或者同一方法)時,須要競爭鎖。但當調用的是不一樣實例的方法時,並不須要競爭鎖。
synchronized用於修飾靜態方法時,執行該方法須要得到的是該類的class對象的內置鎖(一個類只有惟一一個class對象)。調用同一個類的不一樣靜態同步方法時會產生鎖競爭。
synchronized用於修飾代碼塊時,進入同步代碼塊須要得到synchronized關鍵字後面括號內的對象(能夠是實例對象也能夠是class對象)的內置鎖。
鎖的使用是爲了操做臨界資源的正確性,而每每一個方法中並不是全部的代碼都操做臨界資源。換句話說,方法中的代碼每每並不都須要同步。此時建議不使用同步方法,而使用同步代碼塊,只對操做臨界資源的代碼,也即須要同步的代碼加鎖。這樣作的好處是,當一個線程在執行同步代碼塊時,其它線程仍然能夠執行該方法內同步代碼塊之外的部分,充分發揮多線程併發的優點,從而相較於同步整個方法而言提高性能。
釋放Java內置鎖的惟一方式是synchronized方法或者代碼塊執行結束。若某一線程在synchronized方法或代碼塊內發生死鎖,則對應的內置鎖沒法釋放,其它線程也沒法獲取該內置鎖(即進入跟該內置鎖相關的synchronized方法或者代碼塊)。
Java中的重入鎖(即ReentrantLock)與Java內置鎖同樣,是一種排它鎖。使用synchronized的地方必定能夠用ReentrantLock代替。
重入鎖須要顯示請求獲取鎖,並顯示釋放鎖。爲了不得到鎖後,沒有釋放鎖,而形成其它線程沒法得到鎖而形成死鎖,通常建議將釋放鎖操做放在finally塊裏,以下所示。
try{ renentrantLock.lock(); // 用戶操做 } finally { renentrantLock.unlock(); }
若是重入鎖已經被其它線程持有,則當前線程的lock操做會被阻塞。除了lock()方法以外,重入鎖(或者說鎖接口)還提供了其它獲取鎖的方法以實現不一樣的效果。
重入鎖可定義爲公平鎖或非公平鎖,默認實現爲非公平鎖。
如上文《Java進階(二)當咱們說線程安全時,到底在說什麼》所述,鎖能夠保證原子性和可見性。而原子性更可能是針對寫操做而言。對於讀多寫少的場景,一個讀操做無須阻塞其它讀操做,只須要保證讀和寫或者寫與寫不一樣時發生便可。此時,若是使用重入鎖(即排它鎖),對性能影響較大。Java中的讀寫鎖(ReadWriteLock)就是爲這種讀多寫少的場景而創造的。
實際上,ReadWriteLock接口並不是繼承自Lock接口,ReentrantReadWriteLock也只實現了ReadWriteLock接口而未實現Lock接口。ReadLock和WriteLock,是ReentrantReadWriteLock類的靜態內部類,它們實現了Lock接口。
一個ReentrantReadWriteLock實例包含一個ReentrantReadWriteLock.ReadLock實例和一個ReentrantReadWriteLock.WriteLock實例。經過readLock()和writeLock()方法可分別得到讀鎖實例和寫鎖實例,並經過Lock接口提供的獲取鎖方法得到對應的鎖。
讀寫鎖的鎖定規則以下:
package com.test.thread; import java.util.Date; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; 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()方法綁定若干個條件對象。
條件對象提供如下方法以實現不一樣的等待語義
調用條件等待的注意事項
signal()與signalAll()
package com.test.thread; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; 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秒鐘後才返回,而且返回負值。
信號量維護一個許可集,可經過acquire()獲取許可(若無可用許可則阻塞),經過release()釋放許可,從而可能喚醒一個阻塞等待許可的線程。
與互斥鎖相似,信號量限制了同一時間訪問臨界資源的線程的個數,而且信號量也分公平信號量與非公平信號量。而不一樣的是,互斥鎖保證同一時間只會有一個線程訪問臨界資源,而信號量能夠容許同一時間多個線程訪問特定資源。因此信號量並不能保證原子性。
信號量的一個典型使用場景是限制系統訪問量。每一個請求進來後,處理以前都經過acquire獲取許可,若獲取許可成功則處理該請求,若獲取失敗則等待處理或者直接不處理該請求。
信號量的使用方法
注意:與wait/notify和await/signal不一樣,acquire/release徹底與鎖無關,所以acquire等待過程當中,可用許可知足要求時acquire可當即返回,而不用像鎖的wait和條件變量的await那樣從新獲取鎖才能返回。或者能夠理解成,只要可用許可知足需求,就已經得到了鎖。