Object是全部類的基類,它有5個方法組成了等待、通知機制的核心:notify()、notifyAll()、wait()、wait(long) 和wait(long,int)。在java中,全部的類都是從Object繼承而來,所以,全部的類都擁有這些共同的方法可供使用。java
public final void wait() throws InterruptedException,IllegalMonitorStateException
該方法用來將當前線程置入休眠狀態,直到接到通知或中斷爲止。在調用wait()以前,線程必需要得到對象的對象級別的鎖,即只能在同步方法或同步代碼塊中調用wait()方法。進入wait()方法後,當前線程釋放鎖。在從wait()返回前,線程與其餘線程競爭從新得到鎖。若是調用wait()時,沒有持有適當的鎖,則拋出IllegalMonitorStateException,它是RuntimeException的一個子類,所以不須要try-catch結構。git
public final native void notify() throws IllegalMonitorStateException
該方法也要在同步方法或同步代碼塊中調用,即在調用前,線程也必需要得到該對象的對象級別鎖,若是調用notify()時沒有持有適當的鎖,也會拋出IllegalMonitorStateException。github
該方法用來通知那些等待該對象的對象鎖的其餘線程。若是有多個線程等待,則線程規劃器任意挑選其中一個wait()狀態得線程來發出通知,並使它們等待獲取該對象的對象鎖。(notify後,當前線程不會立刻釋放對象鎖,wait所在的線程並不能立刻獲取該對象鎖,要等到程序退出synchronized代碼塊後,當前線程纔會釋放鎖,wait所在的線程才能夠得到該對象鎖)。編程
但不驚動其餘一樣等待該對象notify的線程們,當第一個得到了該對象的wait線程運行完畢以後,它會釋放該對象的鎖,此時若是該對象沒有再次使用notify語句,則即使對象已經空閒,其餘wait狀態等待的線程因爲沒有獲得該對象的通知,會繼續阻塞在wait狀態,直到這個對象發出一個notify或notifyAll。數組
wait狀態等待的是被notify而不是鎖。緩存
public final native void notifyAll() throws IllegalMonitorStateException
該方法與notify()方法的工做方式相同,重要的一點差別:
notifyAll使全部的方法在該對象wait的線程通通退出wait狀態,變成等待獲取該對象上的鎖,一旦該對象鎖被釋放(即notifyAll線程退出同步代碼塊時),它們就會去競爭。若是其中一個線程得到了該對象的鎖,它就會繼續往下執行,在它退出synchronized代碼塊,釋放鎖後,其餘的已經被喚醒的線程將會繼續競爭該鎖,一直進行下去,直到全部被喚醒的線程都執行完畢。安全
public class WaitAndNotify { public static void main(String[] args) throws InterruptedException{ WaitAndNotify wan = new WaitAndNotify(); // synchronized(wan){ // wan.wait(); // System.out.println("wait"); // } new Thread(new Runnable(){ public void run(){ synchronized(wan){ try { wan.wait(); System.out.println("wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable(){ public void run(){ synchronized(wan){ try { wan.wait(); System.out.println("wait"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable(){ public void run(){ synchronized(wan){ wan.notifyAll(); //當notify方法時只執行一個wait、而notifyAll方法將執行兩個wait System.out.println("notify"); } } }).start(); } }
建立以後未啓動服務器
調用了start方法,不過還未被OS調用,或者正在等待CPU時間片。多線程
正在被OS執行。架構
阻塞狀態分三種:
線程執行完畢或出現異常退了run()。
volatile能夠保證線程的可見性並提供必定的有序性,可是沒法保證原子性。
在JVM底層,volatile是採用內存屏障來實現的。
內存屏障是一個cpu指令,他的做用:
若是字段是volatile,java內存模型將在寫操做後插入一個寫屏障指令,在讀操做前插入一個讀屏障指令。這就意味着若是你對一個volatile字段進行寫操做,那麼,一旦你完成寫入,任何訪問這個字段的線程都將會獲得最新的值;在你寫入以前,會保證以前發生的事情已經發生,而且任何更新過的數據值都是可見的。
串行執行任務:在單個線程中串行地執行各項任務。
顯式地爲任務建立線程:經過爲每一個線程建立一個新的線程來提供服務,從而實現更高的響應性。這種方式存在必定缺陷:
線程池
Executor雖然是一個簡單的接口,但它卻爲靈活且強大的異步任務執行框架提供了基礎。它提供了一種標準的方法將任務的提交過程與執行過程解耦開來,應用Runnable來表示任務。同時它還提供了對生命週期的支持。基於生產者-消費者模式,提交任務的操做至關於生產者,執行任務的線程則至關於消費者。
ExecutorService的生命週期有三種狀態:運行、關閉和已終止。
Java並無提供任何機制來安全的終止線程,但它提供了中斷,這是一種協做機制,可以使一個線程終止另外一個線程的當前工做。
Thread的中斷方法:
public class Thread{ public void interrupt(){….} public Boolean isInterrupted(){….} public static Boolean interrupted(){….} }
對中斷的正確理解:他並不會真正的中斷一個正在運行的線程,而只是發出中斷請求,而後由線程在下一個合適的時刻中斷本身。
Thread.sleep()和Object.wait()都會檢查線程什麼時候中斷,而且在發現中斷時提早放回。它們在響應中斷時執行的操做:清除中斷狀態,拋出InterruptedException,表示阻塞操做因爲中斷而提早結束。可以中斷處於阻塞、限期等待、無限期等待等狀態,但不能中斷I/O阻塞跟synchronized鎖阻塞。
在調用interrupted()時返回true,除非像屏蔽這個中斷,否則就必須對它進行處理。若是一個線程的 run() 方法執行一個無限循環,而且沒有執行 sleep() 等會拋出 InterruptedException 的操做,那麼調用線程的 interrupt() 方法就沒法使線程提早結束。
可是調用 interrupt() 方法會設置線程的中斷標記,此時調用 interrupted() 方法會返回 true。所以能夠在循環體中使用 interrupted() 方法來判斷線程是否處於中斷狀態,從而提早結束線程。
線程池:管理一組同構工做線程的資源池,經過重用現有的線程而不是建立新線程,能夠在處理多個請求時分攤在線程建立和銷燬過程當中產生巨大開銷。
類庫提供了一個靈活的線程池以及一些有用的默認配置。能夠用過調用Executors中的靜態工廠方法之一來建立一個線程池:
ThreadPoolExecutor爲一些Executor提供了基本實現,這些Executor時由Executors中的newFixedThreadPool、newCachedThreadPool和newScheduledThreadExecutor等工廠方法返回的。
ThreadPoolExecutor是一個靈活的、穩定的線程池,容許進行各類定製。若是默認的執行策略不能知足需求,那麼能夠經過ThreadPoolExecutor的構造參數來實例化一個對象,並根據本身的需求來定製。
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler){…}
線程池的基本大小,最大大小以及存活時間等因素公共負責線程的建立和銷燬。
在線程池中,若是新請求的到達速率超過線程池的處理速率,那麼新到來的請求將積累起來。在線程池中,這些請求會在一個由Executor管理的Runnable隊列中等待。
ThreadPoolExecutor容許提供一個BlockingQueue來保存等待執行的任務。基本的任務排隊方法有三種:有界隊列、無界隊列和同步移交。隊列的選擇與其餘的配置參數有關,例如線程池的大小等。
當有界隊列被填滿以後,飽和策略開始發揮做用。JDK提供了集中不一樣的RejectedExecutionHadler來實現。
每當線程池須要建立一個線程時,都是經過線程工廠方法來完成的,默認的線程工廠方法將建立一個新的、非守護的線程,而且不包含特殊的配置信息。經過制定一個特定的工廠方法,能夠定製線程池的配置信息。在ThreadFactory中只定義了一個方法newThread,每當線程池須要建立一個新線程時都會調用這個方法。
public interface ThreadFactory { /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ Thread newThread(Runnable r); }
Java5.0增長了一種新的機制:ReentranLock。與內置加鎖機制不一樣的時,Lock提供了一種無條件的、可輪詢的、定時的以及可中斷的鎖獲取操做,全部加鎖和解鎖的方法都是顯式的。
Lock lock = new ReentrantLock(); //... lock.lock(); try { //更新對象狀態 //捕獲異常,並在必要時恢復不變形條件 } finally { lock.unlock(); //不會自動清除鎖 }
當程序的執行控制單元離開被保護的代碼塊時,並不會自動清除鎖。
可定時的與可輪詢的鎖獲取模式是由tryLock方法實現的,與無條件的鎖獲取模式相比,它具備更完善的錯誤恢復機制。在內置鎖中,死鎖是一個很嚴重的問題,恢復程序的惟一方式是從新啓動程序,而防止死鎖的惟一方法就是在構造程序時避免出現不一致的鎖順序。可定時的與可輪詢的鎖提供了另外一種選擇:避免死鎖的發生。
若是不能得到全部須要的鎖,那麼可使用可定時的或可輪詢的鎖獲取方式,從而使你從新得到控制權,它會釋放已經得到的鎖,而後從新嘗試獲取全部鎖。(或其餘操做)
在實現具備時間限制的操做時,定時鎖一樣費用有用:當在帶有時間限制的操做中調用一個阻塞方法時,它能根據剩餘時間來提供一個時限,若是不能在制定的時間內給出結果,那麼就會使程序提早結束。
內置鎖是不可中斷的,故有時將使得實現可取消得任務變得複雜,而顯示鎖能夠中斷,lockInterruptibly方法可以得到鎖的同時保持對中斷的響應。LockInterruptibly優先考慮響應中斷,而不是響應鎖的普通獲取或重入獲取,既容許線程在還等待時就中斷。(lock優先考慮獲取鎖,待獲取鎖成功以後才響應中斷)
在ReentrantLock的構造函數中提供了兩種公平性選擇:建立一個非公平的鎖(默認)或者一個公平的鎖,在公平的鎖上,線程講按照它們發出的請求的順序來獲取鎖,但在非公平鎖上,則容許插隊(當一個線程請求非公平鎖時,同時該鎖的狀態變爲可用,那麼這個線程將跳過隊列中全部的等待線程並得到這個鎖)。
在公平鎖中,若是有一個線程持有這個鎖或者有其餘線程在隊列中等待這個鎖,那麼新發的請求的線程將會放入隊列中,而在非公平鎖中,只有當鎖被某個線程持有時,新發出的請求的線程纔會放入隊列中。
大多數狀況下,非公平鎖的性能要高於公平鎖
Java虛擬機規範試圖定義一種java內存模型(Java Memory Model,JMM)來屏蔽掉各類硬件和操做系統的內存訪問差別,以實現讓Java程序在各類平臺下都能達到一致的內存訪問效果。
計算機系統中處理器上的寄存器的讀寫速度比內存要快幾個數量級別,爲了解決這種矛盾,在它們之間加入高速緩存。
加入高速緩存的存儲交互很好的解決了處理器與內存的速度矛盾,可是也爲計算機系統帶來了更高的複雜度,由於它引入了一個新的問題:緩存一致性。
多處理器系統中,每一個處理器都有本身的高速緩存,而它們又共享同一主內存,當多個處理器的運算任務涉及到同一塊主內存區域時,將可能致使各自的緩存不一致。
爲了解決一致性問題,須要各個處理器訪問緩存時都遵循一些協議,在讀寫時根據協議來進行操做。
Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存以及從內存中取出變量的底層細節。
JMM將全部變量(實例字段、靜態字段、數組對象的元素,線程共享的)都存儲到主內存中,每一個線程有本身的工做內存,工做內存中保存了被線程使用到的變量的主內存的副本拷貝,線程對變量的全部操做都必須在工做內存中進行。
Java內存模型定義了8個操做來完成主內存和工做內存間的交互操做。
Java內存模型保證了read、load、use、assign、store、write、lock和unlock操做具備原子性,例如對一個int類型的變量執行assign複製操做,這個操做就是原子性的。可是Java內存模型容許虛擬機將沒有被volatile修飾的64位數據(long,double)的讀寫操做劃分爲兩個32位的操做來進行,即load、store、read和write操做能夠不具有原子性。
雖然Java內存模型保證了這些操做的原子性,可是int等原子性操做在多線程中仍是會出現線程安全性問題。
可經過兩個方式來解決:
可見性指一個線程修改共享變量的值,其餘線程可以當即得知這個修改。Java內存模型經過在變量修改後將新值同步回主內存中,在變量讀取前從主內存刷新變量值來實現可見性。
主要有三種方式實現可見性:
有序性是指:在本線程內觀察,全部操做都是有序的。在一個線程觀察另外一個線程,全部操做都是無序的,無序是由於發生了指令重排序。在Java內存模型中,容許編譯器和處理器對指令進行重排序,重排序過程當中不會影響到單線程程序的執行,卻會影響多線程併發執行的正確性。
volatile關鍵字經過添加內存屏障的方式禁止重排序,即重排序時不能把後面的指令放在內存屏障以前。
也能夠經過synchronized來保證有序性,它保證每一個時刻只有一個線程執行同步代碼,至關於讓線程順序執行同步代碼。
synchronized和ReentranLock。、
互斥同步最主要的問題是線程阻塞和喚醒所帶來的性能問題,所以這種這種同步燁稱爲阻塞同步。
互斥同步是一種悲觀的併發策略,老是覺得只要不去作正確的同步策略,那就確定會出問題,不管共享數據是否真的會出現競爭,它都須要進行枷鎖。
隨着硬件指令集的發展,咱們可使用基於衝突檢測的樂觀併發策略:先進行操做,若是沒有其餘線程競爭共享資源,那麼操做就成功了,不然採起補償措施(不斷嘗試、知道成功爲止)。這種樂觀的併發策略的許多實現都不須要將線程阻塞,所以這種同步操做稱爲非阻塞同步。
樂觀鎖須要操做和衝突檢測這兩個步驟具有原子性,這裏就不能再使用互斥同步來保證,只能靠硬件來完成。硬件支持的原子性操做最典型的是:比較和交換(Compare-And-swap,CAS)。CAS指令須要3個操做數,分別是內存地址V、舊的預期值A和新值B。當操做完成時,只有內存地址V等值舊的預期值時,纔將V值更新爲B。
多個線程訪問同一個方法的局部變量時,不會出現線程安全問題,由於局部變量存儲在虛擬機棧中,屬於線程私用。
若是一段代碼中所須要的數據必須與其餘代碼共享,那就看看這些共享數據的代碼是否能保證在同一個線程中執行。若是能保證,咱們就能夠把共享數據的可見範圍限制在同一個線程以內,這樣,無須同步也能保證線程之間不出現數據爭用的問題。
符合這種特色的應用並很多見,大部分使用消費隊列的架構模式(如「生產者-消費者」模式)都會將產品的消費過程儘可能在一個線程中消費完。其中最重要的一個應用實例就是經典 Web 交互模型中的「一個請求對應一個服務器線程」(Thread-per-Request)的處理方式,這種處理方式的普遍應用使得不少 Web 服務端應用均可以使用線程本地存儲來解決線程安全問題。
可使用 java.lang.ThreadLocal 類來實現線程本地存儲功能。
synchronized關鍵字在通過編譯以後,會在同步代碼塊先後分別造成monitorenter和monitorexit兩個字節碼指令。
在執行monitorenter指令時,首先嚐試獲取對象的鎖,若是這個對象沒有被鎖定或者當前線程已經擁有了這個鎖,就把鎖的計算器+1,相應的執行完monitorexit指令時將鎖計算器減1,當計算器爲0時,鎖就被釋放。
指JVM對synchronized的優化。
互斥同步進入阻塞狀態的開銷都很大,應該儘可能避免,在許多應用中,共享數據的鎖定狀態只會持續很短的一段時間。自旋鎖的思想是讓一個線程在共享數據的鎖執行忙循環(自旋)一段時間,若是在這段時間內可以得到鎖,就能夠避免進入阻塞狀態。
自旋鎖雖然可以避免進入阻塞狀態從而減小開銷,可是它須要進行忙循環操做佔用cpu時間,它只適用於共享數據的鎖定狀態很短的場景。
在JDK1.6中引入了自適應的自旋鎖,自適應意味着自旋次數再也不是固定的,而是由前一次在同一個鎖上的自旋次數及鎖的擁塞者的狀態來決定。
鎖清除是指對被檢測出不存在競爭的共享數據的鎖進行清除。
鎖清除主要經過逃逸分析來支持,若是堆上的共享數據不可能逃逸出去被其餘線程訪問到,那麼就能夠把他們當成私有數據,也就能夠將他們的鎖清除。
對於一些看起來沒有加鎖的代碼,其實隱式的加了不少鎖,例如一下的字符串拼接代碼就隱式加了鎖:
public static String concatString(String s1, String s2, String s3) { return s1 + s2 + s3; }
String是一個不可變的類,編譯器會對String的拼接進行自動優化。在JDK1.5以前會轉化爲StringBuffer對象連續append()操做。
public static String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
每個append()方法中都有一個同步塊,虛擬機觀察變量sb,很快發現它的動態做用域被限制在concatString方法內部,也就是說,sb的全部引用永遠不會逃逸到contatString()方法以外,其餘線程沒法訪問到它,所以能夠進行鎖清除。
若是一系列的操做都對同一對象反覆加鎖和解鎖,頻繁的加鎖操做就會致使性能消耗。
上一節的示例代碼中連續的append()方法就屬於這種狀況。若是虛擬機探測到由這樣的一串零碎的操做都是對同一個對象加鎖,將會把鎖的範圍擴展(粗化)到整個操做序列的外部。對於上一節的示例代碼就是擴展到第一個 append() 操做以前直至最後一個 append() 操做以後,這樣只須要加鎖一次就能夠了。
輕量級鎖跟偏向鎖是java1.6中引入的,而且規定鎖只能夠升級而不能降級,這就意味着偏向鎖升級成輕量級鎖後不能下降爲偏向鎖,這種策略是爲了提升得到鎖的效率。
Java對象頭一般由兩個部分組成,一個是Mark Word存儲對象的hashCode或者鎖信息,另外一個是Class Metadata Address用於存儲對象類型數據的指針,若是對象是數組,還會有一部分存儲的是數據的長度。
對象頭中的Mark Word佈局
輕量級鎖是相對於傳統的重量級鎖而言,它使用CAS操做來避免重量級鎖使用互斥量的開銷。對於大部分的鎖,在整個同步週期內都是不存在晶振的,所以也就不須要使用互斥量進行同步,能夠先採用CAS操做進行同步,若是CAS失敗了再改用互斥量進行同步。
當嘗試獲取一個鎖對象時,若是鎖對象標記爲0 01,說明鎖對象的鎖未鎖定(unlock)狀態,此時虛擬機在當前線程的虛擬機棧建立Lock Record,而後使用CAS操做將對象的Mark Word更新爲Lock Record指針。若是CAS操做成功了,那麼線程就獲取了該對象上的鎖,而且對象的Mark Word的鎖標記變爲00,表示該對象處於輕量級鎖狀態。
若是CAS操做失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的虛擬機棧,若是是的話說明當前線程已經擁有了這個鎖對象,那就能夠直接進入同步塊執行,不然說明這個鎖對象已經被其餘線程搶佔了。若是有兩個以上的線程競爭同一個鎖,那輕量級鎖就再也不有效,要膨脹爲重量級鎖。
輕量級鎖的步驟以下:
偏向鎖的思想是偏向於讓第一個獲取鎖對象的線程,在以後的獲取該鎖就再也不須要進行同步操做,甚至連CAS操做也不須要。
當鎖對象第一次被線程得到的時候,進入偏向狀態,標記爲1 01。同時使用CAS操做將線程ID記錄到Mark Word中,若是CAS操做成功,這個線程之後每次進入這個鎖相關的同步塊就不須要再進行任何同步操做。
當有另外一個線程去嘗試獲取這個鎖對象,偏向狀態就宣告結束,此時偏向取消後恢復到未鎖定狀態或者輕量級鎖狀態。
偏向鎖得到鎖的步驟分爲: