程安全是併發編程中的重要關注點,應該注意到的是,形成線程安全問題的主要誘因有兩點,一是存在共享數據(也稱臨界資源),二是存在多條線程共同操做共享數據。所以爲了解決這個問題,咱們可能須要這樣一個方案,當存在多個線程操做共享數據時,須要保證同一時刻有且只有一個線程在操做共享數據,其餘線程必須等到該線程處理完數據後再進行,這種方式有個高尚的名稱叫互斥鎖,即能達到互斥訪問目的的鎖,也就是說當一個共享數據被當前正在訪問的線程加上互斥鎖後,在同一個時刻,其餘線程只能處於等待的狀態,直到當前線程處理完畢釋放該鎖。在 Java 中,關鍵字 synchronized能夠保證在同一個時刻,只有一個線程能夠執行某個方法或者某個代碼塊(主要是對方法或者代碼塊中存在共享數據的操做),同時咱們還應該注意到synchronized另一個重要的做用,synchronized可保證一個線程的變化(主要是共享數據的變化)被其餘線程所看到(保證可見性,徹底能夠替代Volatile功能),這點確實也是很重要的。java
注:文章有點長能夠根據目錄來看!編程
synchronized關鍵字最主要有如下3種應用方式,下面分別介紹安全
修飾實例方法,做用於當前實例加鎖,進入同步代碼前要得到當前實例的鎖bash
修飾靜態方法,做用於當前類對象加鎖,進入同步代碼前要得到當前類對象的鎖多線程
修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要得到給定對象的鎖。併發
所謂的實例對象鎖就是用synchronized修飾實例對象中的實例方法,注意是實例方法不包括靜態方法,以下app
public class AccountingSync implements Runnable{
//共享資源(臨界資源)
static int i=0;
/** * synchronized 修飾實例方法 */
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
AccountingSync instance=new AccountingSync();
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
/** * 輸出結果: * 2000000 */
}
複製代碼
上述代碼中,咱們開啓兩個線程操做同一個共享資源即變量i,因爲i++;操做並不具有原子性,該操做是先讀取值,而後寫回一個新值,至關於原來的值加上1,分兩步完成,若是第二個線程在第一個線程讀取舊值和寫回新值期間讀取i的域值,那麼第二個線程就會與第一個線程一塊兒看到同一個值,並執行相同值的加1操做,這也就形成了線程安全失敗,所以對於increase方法必須使用synchronized修飾,以便保證線程安全。此時咱們應該注意到synchronized修飾的是實例方法increase,在這樣的狀況下,當前線程的鎖即是實例對象instance,注意Java中的線程同步鎖能夠是任意對象。從代碼執行結果來看確實是正確的,假若咱們沒有使用synchronized關鍵字,其最終輸出結果就極可能小於2000000,這即是synchronized關鍵字的做用。這裏咱們還須要意識到,當一個線程正在訪問一個對象的 synchronized 實例方法,那麼其餘線程不能訪問該對象的其餘 synchronized 方法,畢竟一個對象只有一把鎖,當一個線程獲取了該對象的鎖以後,其餘線程沒法獲取該對象的鎖,因此沒法訪問該對象的其餘synchronized實例方法,可是其餘線程仍是能夠訪問該實例對象的其餘非synchronized方法,固然若是是一個線程 A 須要訪問實例對象 obj1 的 synchronized 方法 f1(當前對象鎖是obj1),另外一個線程 B 須要訪問實例對象 obj2 的 synchronized 方法 f2(當前對象鎖是obj2),這樣是容許的,由於兩個實例對象鎖並不一樣相同,此時若是兩個線程操做數據並不是共享的,線程安全是有保障的,遺憾的是若是兩個線程操做的是共享數據,那麼線程安全就有可能沒法保證了,以下代碼將演示出該現象ide
public class AccountingSyncBad implements Runnable{
static int i=0;
public synchronized void increase(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新實例
Thread t1=new Thread(new AccountingSyncBad());
//new新實例
Thread t2=new Thread(new AccountingSyncBad());
t1.start();
t2.start();
//join含義:當前線程A等待thread線程終止以後才能從thread.join()返回
t1.join();
t2.join();
System.out.println(i);
}
}
複製代碼
上述代碼與前面不一樣的是咱們同時建立了兩個新實例AccountingSyncBad,而後啓動兩個不一樣的線程對共享變量i進行操做,但很遺憾操做結果是1452317而不是指望結果2000000,由於上述代碼犯了嚴重的錯誤,雖然咱們使用synchronized修飾了increase方法,但卻new了兩個不一樣的實例對象,這也就意味着存在着兩個不一樣的實例對象鎖,所以t1和t2都會進入各自的對象鎖,也就是說t1和t2線程使用的是不一樣的鎖,所以線程安全是沒法保證的。解決這種困境的的方式是將synchronized做用於靜態的increase方法,這樣的話,對象鎖就當前類對象,因爲不管建立多少個實例對象,但對於的類對象擁有隻有一個,全部在這樣的狀況下對象鎖就是惟一的。下面咱們看看如何使用將synchronized做用於靜態的increase方法。函數
當synchronized做用於靜態方法時,其鎖就是當前類的class對象鎖。因爲靜態成員不專屬於任何一個實例對象,是類成員,所以經過class對象鎖能夠控制靜態 成員的併發操做。須要注意的是若是一個線程A調用一個實例對象的非static synchronized方法,而線程B須要調用這個實例對象所屬類的靜態 synchronized方法,是容許的,不會發生互斥現象,由於訪問靜態 synchronized 方法佔用的鎖是當前類的class對象,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖,看以下代碼性能
public class AccountingSyncClass implements Runnable{
static int i=0;
/** * 做用於靜態方法,鎖是當前class對象,也就是 * AccountingSyncClass類對應的class對象 */
public static synchronized void increase(){
i++;
}
/** * 非靜態,訪問時鎖不同不會發生互斥 */
public synchronized void increase4Obj(){
i++;
}
@Override
public void run() {
for(int j=0;j<1000000;j++){
increase();
}
}
public static void main(String[] args) throws InterruptedException {
//new新實例
Thread t1=new Thread(new AccountingSyncClass());
//new心事了
Thread t2=new Thread(new AccountingSyncClass());
//啓動線程
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
複製代碼
因爲synchronized關鍵字修飾的是靜態increase方法,與修飾實例方法不一樣的是,其鎖對象是當前類的class對象。注意代碼中的increase4Obj方法是實例方法,其對象鎖是當前實例對象,若是別的線程調用該方法,將不會產生互斥現象,畢竟鎖對象不一樣,但咱們應該意識到這種狀況下可能會發現線程安全問題(操做了共享靜態變量i)。
除了使用關鍵字修飾實例方法和靜態方法外,還可使用同步代碼塊,在某些狀況下,咱們編寫的方法體可能比較大,同時存在一些比較耗時的操做,而須要同步的代碼又只有一小部分,若是直接對整個方法進行同步操做,可能會得不償失,此時咱們可使用同步代碼塊的方式對須要同步的代碼進行包裹,這樣就無需對整個方法進行同步操做了,同步代碼塊的使用示例以下:
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
@Override
public void run() {
//省略其餘耗時操做....
//使用同步代碼塊對變量i進行同步操做,鎖對象爲instance
synchronized(instance){
for(int j=0;j<1000000;j++){
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
複製代碼
從代碼看出,將synchronized做用於一個給定的實例對象instance,即當前實例對象就是鎖對象,每次當線程進入synchronized包裹的代碼塊時就會要求當前線程持有instance實例對象鎖,若是當前有其餘線程正持有該對象鎖,那麼新到的線程就必須等待,這樣也就保證了每次只有一個線程執行i++;操做。固然除了instance做爲對象外,咱們還可使用this對象(表明當前實例)或者當前類的class對象做爲鎖,以下代碼:
//this,當前實例對象鎖
synchronized(this){
for(int j=0;j<1000000;j++){
i++;
}
}
//class對象鎖
synchronized(AccountingSync.class){
for(int j=0;j<1000000;j++){
i++;
}
}
複製代碼
瞭解完synchronized的基本含義及其使用方式後,下面咱們將進一步深刻理解synchronized的底層實現原理。
Java 虛擬機中的同步(Synchronization)基於進入和退出管程(Monitor)對象實現, 不管是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)仍是隱式同步都是如此。在 Java 語言中,同步用的最多的地方多是被 synchronized 修飾的同步方法。同步方法 並非由 monitorenter 和 monitorexit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法的 ACC_SYNCHRONIZED 標誌來隱式實現的,關於這點,稍後詳細分析。下面先來了解一個概念Java對象頭,這對深刻理解synchronized實現原理很是關鍵。
若是對上面的執行結果還有疑問,也先不用急,咱們先來了解Synchronized的原理,再回頭上面的問題就一目瞭然了。咱們先經過反編譯下面的代碼來看看Synchronized是如何實現對代碼塊進行同步的:
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
複製代碼
反編譯結果:
關於這兩條指令的做用,咱們直接參考JVM規範中描述:
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership. 複製代碼
這段話的大概意思爲:
每一個對象有一個監視器鎖(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的全部權,過程以下:
若是monitor的進入數爲0,則該線程進入monitor,而後將進入數設置爲1,該線程即爲monitor的全部者。
若是線程已經佔有該monitor,只是從新進入,則進入monitor的進入數加1.
若是其餘線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數爲0,再從新嘗試獲取monitor的全部權。
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
複製代碼
這段話的大概意思爲:
執行monitorexit的線程必須是objectref所對應的monitor的全部者。
指令執行時,monitor的進入數減1,若是減1後進入數爲0,那線程退出monitor,再也不是這個monitor的全部者。其餘被這個monitor阻塞的線程能夠嘗試去獲取這個 monitor 的全部權。
經過這兩段描述,咱們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是經過一個monitor的對象來完成,其實wait/notify等方法也依賴於monitor對象,這就是爲何只有在同步的塊或者方法中才能調用wait/notify等方法,不然會拋出java.lang.IllegalMonitorStateException的異常的緣由。
咱們再來看一下同步方法的反編譯結果:
源代碼:
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
複製代碼
反編譯結果:
從反編譯的結果來看,方法的同步並無經過指令monitorenter和monitorexit來完成(理論上其實也能夠經過這兩條指令來實現),不過相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據該標示符來實現方法的同步的:當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,若是設置了,執行線程將先獲取monitor,獲取成功以後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其餘任何線程都沒法再得到同一個monitor對象。 其實本質上沒有區別,只是方法的同步是一種隱式的方式來實現,無需經過字節碼來完成。
從互斥鎖的設計上來講,當一個線程試圖操做一個由其餘線程持有的對象鎖的臨界資源時,將會處於阻塞狀態,但當一個線程再次請求本身持有對象鎖的臨界資源時,這種狀況屬於重入鎖,請求將會成功,在java中synchronized是基於原子性的內部鎖機制,是可重入的,所以在一個線程調用synchronized方法的同時在其方法體內部調用該對象另外一個synchronized方法,也就是說一個線程獲得一個對象鎖後再次請求該對象鎖,是容許的,這就是synchronized的可重入性。以下:
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
static int j=0;
@Override
public void run() {
for(int j=0;j<1000000;j++){
//this,當前實例對象鎖
synchronized(this){
i++;
increase();//synchronized的可重入性
}
}
}
public synchronized void increase(){
j++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
複製代碼
正如代碼所演示的,在獲取當前實例對象鎖後進入synchronized代碼塊執行同步代碼,並在代碼塊中調用了當前實例對象的另一個synchronized方法,再次請求當前實例鎖時,將被容許,進而執行方法體代碼,這就是重入鎖最直接的體現,須要特別注意另一種狀況,當子類繼承父類時,子類也是能夠經過可重入鎖調用父類的同步方法。注意因爲synchronized是基於monitor實現的,所以每次重入,monitor中的計數器仍會加1。
線程中斷
正如中斷二字所表達的意義,在線程運行(run方法)中間打斷它,在Java中,提供瞭如下3個有關線程中斷的方法
//中斷線程(實例方法)
public void Thread.interrupt();
//判斷線程是否被中斷(實例方法)
public boolean Thread.isInterrupted();
//判斷是否被中斷並清除當前中斷狀態(靜態方法)
public static boolean Thread.interrupted();
複製代碼
當一個線程處於被阻塞狀態或者試圖執行一個阻塞操做時,使用Thread.interrupt()方式中斷該線程,注意此時將會拋出一個InterruptedException的異常,同時中斷狀態將會被複位(由中斷狀態改成非中斷狀態),以下代碼將演示該過程:
public class InterruputSleepThread3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
//while在try中,經過異常中斷就能夠退出run循環
try {
while (true) {
//當前線程處於阻塞狀態,異常必須捕捉處理,沒法往外拋出
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
boolean interrupt = this.isInterrupted();
//中斷狀態被複位
System.out.println("interrupt:"+interrupt);
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
//中斷處於阻塞狀態的線程
t1.interrupt();
/** * 輸出結果: Interruted When Sleep interrupt:false */
}
}
複製代碼
如上述代碼所示,咱們建立一個線程,並在線程中調用了sleep方法從而使用線程進入阻塞狀態,啓動線程後,調用線程實例對象的interrupt方法中斷阻塞異常,並拋出InterruptedException異常,此時中斷狀態也將被複位。這裏有些人可能會詫異,爲何不用Thread.sleep(2000);而是用TimeUnit.SECONDS.sleep(2);其實緣由很簡單,前者使用時並無明確的單位說明,然後者很是明確表達秒的單位,事實上後者的內部實現最終仍是調用了Thread.sleep(2000);,但爲了編寫的代碼語義更清晰,建議使用TimeUnit.SECONDS.sleep(2);的方式,注意TimeUnit是個枚舉類型。ok~,除了阻塞中斷的情景,咱們還可能會遇處處於運行期且非阻塞的狀態的線程,這種狀況下,直接調用Thread.interrupt()中斷線程是不會獲得任響應的,以下代碼,將沒法中斷非阻塞狀態下的線程:
public class InterruputThread {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
System.out.println("未被中斷");
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
/** * 輸出結果(無限執行): 未被中斷 未被中斷 未被中斷 ...... */
}
}
複製代碼
雖然咱們調用了interrupt方法,但線程t1並未被中斷,由於處於非阻塞狀態的線程須要咱們手動進行中斷檢測並結束程序,改進後代碼以下:
public class InterruputThread {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
//判斷當前線程是否被中斷
if (this.isInterrupted()){
System.out.println("線程中斷");
break;
}
}
System.out.println("已跳出循環,線程中斷!");
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
/** * 輸出結果: 線程中斷 已跳出循環,線程中斷! */
}
}
複製代碼
是的,咱們在代碼中使用了實例方法isInterrupted判斷線程是否已被中斷,若是被中斷將跳出循環以此結束線程。綜合所述,能夠簡單總結一下中斷兩種狀況,一種是當線程處於阻塞狀態或者試圖執行一個阻塞操做時,咱們可使用實例方法interrupt()進行線程中斷,執行中斷操做後將會拋出interruptException異常(該異常必須捕捉沒法向外拋出)並將中斷狀態復位,另一種是當線程處於運行狀態時,咱們也可調用實例方法interrupt()進行線程中斷,但同時必須手動判斷中斷狀態,並編寫中斷線程的代碼(其實就是結束run方法體的代碼)。有時咱們在編碼時可能須要兼顧以上兩種狀況,那麼就能夠以下編寫:
public void run(){
try {
//判斷當前線程是否已中斷,注意interrupted方法是靜態的,執行後會對中斷狀態進行復位
while (!Thread.interrupted()) {
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
}
}
複製代碼
事實上線程的中斷操做對於正在等待獲取的鎖對象的synchronized方法或者代碼塊並不起做用,也就是對於synchronized來講,若是一個線程在等待鎖,那麼結果只有兩種,要麼它得到這把鎖繼續執行,要麼它就保存等待,即便調用中斷線程的方法,也不會生效。演示代碼以下
public class SynchronizedBlocked implements Runnable{
public synchronized void f() {
System.out.println("Trying to call f()");
while(true) // Never releases lock
Thread.yield();
}
/** * 在構造器中建立新線程並啓動獲取對象鎖 */
public SynchronizedBlocked() {
//該線程已持有當前實例鎖
new Thread() {
public void run() {
f(); // Lock acquired by this thread
}
}.start();
}
public void run() {
//中斷判斷
while (true) {
if (Thread.interrupted()) {
System.out.println("中斷線程!!");
break;
} else {
f();
}
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedBlocked sync = new SynchronizedBlocked();
Thread t = new Thread(sync);
//啓動後調用f()方法,沒法獲取當前實例鎖處於等待狀態
t.start();
TimeUnit.SECONDS.sleep(1);
//中斷線程,沒法生效
t.interrupt();
}
}
複製代碼
咱們在SynchronizedBlocked構造函數中建立一個新線程並啓動獲取調用f()獲取到當前實例鎖,因爲SynchronizedBlocked自身也是線程,啓動後在其run方法中也調用了f(),但因爲對象鎖被其餘線程佔用,致使t線程只能等到鎖,此時咱們調用了t.interrupt();
但並不能中斷線程。
所謂等待喚醒機制本篇主要指的是notify/notifyAll和wait方法,在使用這3個方法時,必須處於synchronized代碼塊或者synchronized方法中,不然就會拋出IllegalMonitorStateException異常,這是由於調用這幾個方法前必須拿到當前對象的監視器monitor對象,也就是說notify/notifyAll和wait方法依賴於monitor對象,在前面的分析中,咱們知道monitor 存在於對象頭的Mark Word 中(存儲monitor引用指針),而synchronized關鍵字能夠獲取 monitor ,這也就是爲何notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調用的緣由。
synchronized (obj) {
obj.wait();
obj.notify();
obj.notifyAll();
}
複製代碼
須要特別理解的一點是,與sleep方法不一樣的是wait方法調用完成後,線程將被暫停,但wait方法將會釋放當前持有的監視器鎖(monitor),直到有線程調用notify/notifyAll方法後方能繼續執行,而sleep方法只讓線程休眠並不釋放鎖。同時notify/notifyAll方法調用後,並不會立刻釋放監視器鎖,而是在相應的synchronized(){}/synchronized方法執行結束後才自動釋放鎖。
鎖的狀態總共有四種,無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。隨着鎖的競爭,鎖能夠從偏向鎖升級到輕量級鎖,再升級的重量級鎖,可是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級,關於重量級鎖,前面咱們已詳細分析過,下面咱們將介紹偏向鎖和輕量級鎖以及JVM的其餘優化手段,這裏並不打算深刻到每一個鎖的實現和轉換過程更多地是闡述Java虛擬機所提供的每一個鎖的核心優化思想,畢竟涉及到具體過程比較繁瑣,如需瞭解詳細過程能夠查閱《深刻理解Java虛擬機原理》。
偏向鎖是Java 6以後加入的新鎖,它是一種針對加鎖操做的優化手段,通過研究發現,在大多數狀況下,鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,所以爲了減小同一線程獲取鎖(會涉及到一些CAS操做,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,若是一個線程得到了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再作任何同步操做,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操做,從而也就提供程序的性能。因此,對於沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續屢次是同一個線程申請相同的鎖。可是對於鎖競爭比較激烈的場合,偏向鎖就失效了,由於這樣場合極有可能每次申請鎖的線程都是不相同的,所以這種場合下不該該使用偏向鎖,不然會得不償失,須要注意的是,偏向鎖失敗後,並不會當即膨脹爲重量級鎖,而是先升級爲輕量級鎖。下面咱們接着瞭解輕量級鎖。
假若偏向鎖失敗,虛擬機並不會當即升級爲重量級鎖,它還會嘗試使用一種稱爲輕量級鎖的優化手段(1.6以後加入的),此時Mark Word 的結構也變爲輕量級鎖的結構。輕量級鎖可以提高程序性能的依據是「對絕大部分的鎖,在整個同步週期內都不存在競爭」,注意這是經驗數據。須要瞭解的是,輕量級鎖所適應的場景是線程交替執行同步塊的場合,若是存在同一時間訪問同一鎖的場合,就會致使輕量級鎖膨脹爲重量級鎖。
輕量級鎖失敗後,虛擬機爲了不線程真實地在操做系統層面掛起,還會進行一項稱爲自旋鎖的優化手段。這是基於在大多數狀況下,線程持有鎖的時間都不會太長,若是直接掛起操做系統層面的線程可能會得不償失,畢竟操做系統實現線程之間的切換時須要從用戶態轉換到核心態,這個狀態之間的轉換須要相對比較長的時間,時間成本相對較高,所以自旋鎖會假設在不久未來,當前的線程能夠得到鎖,所以虛擬機會讓當前想要獲取鎖的線程作幾個空循環(這也是稱爲自旋的緣由),通常不會過久,多是50個循環或100循環,在通過若干次循環後,若是獲得鎖,就順利進入臨界區。若是還不能得到鎖,那就會將線程在操做系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是能夠提高效率的。最後沒辦法也就只能升級爲重量級鎖了。
消除鎖是虛擬機另一種鎖的優化,這種優化更完全,Java虛擬機在JIT編譯時(能夠簡單理解爲當某段代碼即將第一次被執行時進行編譯,又稱即時編譯),經過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,經過這種方式消除沒有必要的鎖,能夠節省毫無心義的請求鎖時間,以下StringBuffer的append是一個同步方法,可是在add方法中的StringBuffer屬於一個局部變量,而且不會被其餘線程所使用,所以StringBuffer不可能存在共享資源競爭的情景,JVM會自動將其鎖消除。
即synchronized,一直等待線程施放鎖後才能夠拿到資源。