溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Synchronized和Lock的區(qū)別有哪些

發(fā)布時(shí)間:2020-10-29 15:45:00 來(lái)源:億速云 閱讀:190 作者:Leah 欄目:開(kāi)發(fā)技術(shù)

本篇文章給大家分享的是有關(guān)Synchronized和Lock的區(qū)別有哪些,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話(huà)不多說(shuō),跟著小編一起來(lái)看看吧。

Synchronized是內(nèi)置的java關(guān)鍵字,Lock是一個(gè)java類(lèi)。

Synchronized無(wú)法判斷是否獲取到了鎖,Lock可以判斷是否獲取到了鎖。

Synchronized會(huì)自動(dòng)釋放鎖,Lock必須手動(dòng)釋放鎖。

Synchronized線程1獲得鎖之后阻塞,等待鎖的線程2會(huì)一直等下去(死等)。Lock不一定會(huì)死等。

Synchronized可重入鎖、不可中斷、非公平鎖。Lock是可重入鎖、選擇是否可中斷、可以選擇是否公平。

Synchronized適合鎖少量的代碼同步問(wèn)題。Lock適合鎖大量的同步代碼。

補(bǔ)充知識(shí):java synchronized關(guān)鍵字的用法以及鎖的等級(jí):方法鎖、對(duì)象鎖、類(lèi)鎖

首先說(shuō)明一下:方法鎖和對(duì)象鎖說(shuō)的是一個(gè)東西,即只有方法鎖或?qū)ο箧i 和類(lèi)鎖兩種鎖

在java編程中,經(jīng)常需要用到同步,而用得最多的也許是synchronized關(guān)鍵字了,下面看看這個(gè)關(guān)鍵字的用法。

因?yàn)閟ynchronized關(guān)鍵字涉及到鎖的概念,所以先來(lái)了解一些相關(guān)的鎖知識(shí)。

java的內(nèi)置鎖:每個(gè)java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖,這些鎖成為內(nèi)置鎖。線程進(jìn)入同步代碼塊或方法的時(shí)候會(huì)自動(dòng)獲得該鎖,在退出同步代碼塊或方法時(shí)會(huì)釋放該鎖。獲得內(nèi)置鎖的唯一途徑就是進(jìn)入這個(gè)鎖的保護(hù)的同步代碼塊或方法。

java內(nèi)置鎖是一個(gè)互斥鎖,這就是意味著最多只有一個(gè)線程能夠獲得該鎖,當(dāng)線程A嘗試去獲得線程B持有的內(nèi)置鎖時(shí),線程A必須等待或者阻塞,知道線程B釋放這個(gè)鎖,如果B線程不釋放這個(gè)鎖,那么A線程將永遠(yuǎn)等待下去。

java的對(duì)象鎖和類(lèi)鎖:java的對(duì)象鎖和類(lèi)鎖在鎖的概念上基本上和內(nèi)置鎖是一致的,但是,兩個(gè)鎖實(shí)際是有很大的區(qū)別的,對(duì)象鎖是用于對(duì)象實(shí)例方法,或者一個(gè)對(duì)象實(shí)例上的,類(lèi)鎖是用于類(lèi)的靜態(tài)方法或者一個(gè)類(lèi)的class對(duì)象上的。我們知道,類(lèi)的對(duì)象實(shí)例可以有很多個(gè),但是每個(gè)類(lèi)只有一個(gè)class對(duì)象,所以不同對(duì)象實(shí)例的對(duì)象鎖是互不干擾的,但是每個(gè)類(lèi)只有一個(gè)類(lèi)鎖。但是有一點(diǎn)必須注意的是,其實(shí)類(lèi)鎖只是一個(gè)概念上的東西,并不是真實(shí)存在的,它只是用來(lái)幫助我們理解鎖定實(shí)例方法和靜態(tài)方法的區(qū)別的

上面已經(jīng)對(duì)鎖的一些概念有了一點(diǎn)了解,下面探討synchronized關(guān)鍵字的用法。

synchronized的用法:synchronized修飾方法和synchronized修飾代碼塊。

下面分別分析這兩種用法在對(duì)象鎖和類(lèi)鎖上的效果。

對(duì)象鎖的synchronized修飾方法和代碼塊:

public class TestSynchronized  
{  
  public void test1()  
  {  
     synchronized(this)  
     {  
       int i = 5;  
       while( i-- > 0)  
       {  
          System.out.println(Thread.currentThread().getName() + " : " + i);  
          try  
          {  
            Thread.sleep(500);  
          }  
          catch (InterruptedException ie)  
          {  
          }  
       }  
     }  
  }  
   
  public synchronized void test2()  
  {  
     int i = 5;  
     while( i-- > 0)  
     {  
       System.out.println(Thread.currentThread().getName() + " : " + i);  
       try  
       {  
          Thread.sleep(500);  
       }  
       catch (InterruptedException ie)  
       {  
       }  
     }  
  }  
   
  public static void main(String[] args)  
  {  
     final TestSynchronized myt2 = new TestSynchronized();  
     Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );  
     Thread test2 = new Thread( new Runnable() { public void run() { myt2.test2();  } }, "test2" );  
     test1.start();;  
     test2.start();  
//     TestRunnable tr=new TestRunnable(); 
//     Thread test3=new Thread(tr); 
//     test3.start(); 
  }   
} 
test2 : 4 
test2 : 3 
test2 : 2 
test2 : 1 
test2 : 0 
test1 : 4 
test1 : 3 
test1 : 2 
test1 : 1 
test1 : 0 

上述的代碼,第一個(gè)方法時(shí)用了同步代碼塊的方式進(jìn)行同步,傳入的對(duì)象實(shí)例是this,表明是當(dāng)前對(duì)象,當(dāng)然,如果需要同步其他對(duì)象實(shí)例,也不可傳入其他對(duì)象的實(shí)例;第二個(gè)方法是修飾方法的方式進(jìn)行同步。因?yàn)榈谝粋€(gè)同步代碼塊傳入的this,所以?xún)蓚€(gè)同步代碼所需要獲得的對(duì)象鎖都是同一個(gè)對(duì)象鎖,下面main方法時(shí)分別開(kāi)啟兩個(gè)線程,分別調(diào)用test1和test2方法,那么兩個(gè)線程都需要獲得該對(duì)象鎖,另一個(gè)線程必須等待。上面也給出了運(yùn)行的結(jié)果可以看到:直到test2線程執(zhí)行完畢,釋放掉鎖,test1線程才開(kāi)始執(zhí)行。(可能這個(gè)結(jié)果有人會(huì)有疑問(wèn),代碼里面明明是先開(kāi)啟test1線程,為什么先執(zhí)行的是test2呢?這是因?yàn)閖ava編譯器在編譯成字節(jié)碼的時(shí)候,會(huì)對(duì)代碼進(jìn)行一個(gè)重排序,也就是說(shuō),編譯器會(huì)根據(jù)實(shí)際情況對(duì)代碼進(jìn)行一個(gè)合理的排序,編譯前代碼寫(xiě)在前面,在編譯后的字節(jié)碼不一定排在前面,所以這種運(yùn)行結(jié)果是正常的, 這里是題外話(huà),最主要是檢驗(yàn)synchronized的用法的正確性)

如果我們把test2方法的synchronized關(guān)鍵字去掉,執(zhí)行結(jié)果會(huì)如何呢?

test1 : 4 
test2 : 4 
test2 : 3 
test1 : 3 
test1 : 2 
test2 : 2 
test2 : 1 
test1 : 1 
test2 : 0 
test1 : 0 

上面是執(zhí)行結(jié)果,我們可以看到,結(jié)果輸出是交替著進(jìn)行輸出的,這是因?yàn)?,某個(gè)線程得到了對(duì)象鎖,但是另一個(gè)線程還是可以訪問(wèn)沒(méi)有進(jìn)行同步的方法或者代碼。進(jìn)行了同步的方法(加鎖方法)和沒(méi)有進(jìn)行同步的方法(普通方法)是互不影響的,一個(gè)線程進(jìn)入了同步方法,得到了對(duì)象鎖,其他線程還是可以訪問(wèn)那些沒(méi)有同步的方法(普通方法)。這里涉及到內(nèi)置鎖的一個(gè)概念(此概念出自java并發(fā)編程實(shí)戰(zhàn)第二章):對(duì)象的內(nèi)置鎖和對(duì)象的狀態(tài)之間是沒(méi)有內(nèi)在的關(guān)聯(lián)的,雖然大多數(shù)類(lèi)都將內(nèi)置鎖用做一種有效的加鎖機(jī)制,但對(duì)象的域并不一定通過(guò)內(nèi)置鎖來(lái)保護(hù)。當(dāng)獲取到與對(duì)象關(guān)聯(lián)的內(nèi)置鎖時(shí),并不能阻止其他線程訪問(wèn)該對(duì)象,當(dāng)某個(gè)線程獲得對(duì)象的鎖之后,只能阻止其他線程獲得同一個(gè)鎖。之所以每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖,是為了免去顯式地創(chuàng)建鎖對(duì)象。

所以synchronized只是一個(gè)內(nèi)置鎖的加鎖機(jī)制,當(dāng)某個(gè)方法加上synchronized關(guān)鍵字后,就表明要獲得該內(nèi)置鎖才能執(zhí)行,并不能阻止其他線程訪問(wèn)不需要獲得該內(nèi)置鎖的方法。

類(lèi)鎖的修飾(靜態(tài))方法和代碼塊:

public class TestSynchronized  
{  
  public void test1()  
  {  
     synchronized(TestSynchronized.class)  
     {  
       int i = 5;  
       while( i-- > 0)  
       {  
          System.out.println(Thread.currentThread().getName() + " : " + i);  
          try  
          {  
            Thread.sleep(500);  
          }  
          catch (InterruptedException ie)  
          {  
          }  
       }  
     }  
  }  
   
  public static synchronized void test2()  
  {  
     int i = 5;  
     while( i-- > 0)  
     {  
       System.out.println(Thread.currentThread().getName() + " : " + i);  
       try  
       {  
          Thread.sleep(500);  
       }  
       catch (InterruptedException ie)  
       {  
       }  
     }  
  }  
   
  public static void main(String[] args)  
  {  
     final TestSynchronized myt2 = new TestSynchronized();  
     Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );  
     Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2();  } }, "test2" );  
     test1.start();  
     test2.start();  
//     TestRunnable tr=new TestRunnable(); 
//     Thread test3=new Thread(tr); 
//     test3.start(); 
  }   
} 
test1 : 4 
test1 : 3 
test1 : 2 
test1 : 1 
test1 : 0 
test2 : 4 
test2 : 3 
test2 : 2 
test2 : 1 
test2 : 0 

其實(shí),類(lèi)鎖修飾方法和代碼塊的效果和對(duì)象鎖是一樣的,因?yàn)轭?lèi)鎖只是一個(gè)抽象出來(lái)的概念,只是為了區(qū)別靜態(tài)方法的特點(diǎn),因?yàn)殪o態(tài)方法是所有對(duì)象實(shí)例共用的,所以對(duì)應(yīng)著synchronized修飾的靜態(tài)方法的鎖也是唯一的,所以抽象出來(lái)個(gè)類(lèi)鎖。其實(shí)這里的重點(diǎn)在下面這塊代碼,synchronized同時(shí)修飾靜態(tài)和非靜態(tài)方法

public class TestSynchronized  
{  
  public synchronized void test1()  
  {  
       int i = 5;  
       while( i-- > 0)  
       {  
          System.out.println(Thread.currentThread().getName() + " : " + i);  
          try  
          {  
            Thread.sleep(500);  
          }  
          catch (InterruptedException ie)  
          {  
          }  
       }  
  }  
   
  public static synchronized void test2()  
  {  
     int i = 5;  
     while( i-- > 0)  
     {  
       System.out.println(Thread.currentThread().getName() + " : " + i);  
       try  
       {  
          Thread.sleep(500);  
       }  
       catch (InterruptedException ie)  
       {  
       }  
     }  
  }  
   
  public static void main(String[] args)  
  {  
     final TestSynchronized myt2 = new TestSynchronized();  
     Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );  
     Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2();  } }, "test2" );  
     test1.start();  
     test2.start();  
//     TestRunnable tr=new TestRunnable(); 
//     Thread test3=new Thread(tr); 
//     test3.start(); 
  }  
  
} 
test1 : 4 
test2 : 4 
test1 : 3 
test2 : 3 
test2 : 2 
test1 : 2 
test2 : 1 
test1 : 1 
test1 : 0 
test2 : 0 

上面代碼synchronized同時(shí)修飾靜態(tài)方法和實(shí)例方法,但是運(yùn)行結(jié)果是交替進(jìn)行的,這證明了類(lèi)鎖和對(duì)象鎖是兩個(gè)不一樣的鎖,控制著不同的區(qū)域,它們是互不干擾的。同樣,線程獲得對(duì)象鎖的同時(shí),也可以獲得該類(lèi)鎖,即同時(shí)獲得兩個(gè)鎖,這是允許的。

到這里,對(duì)synchronized的用法已經(jīng)有了一定的了解。這時(shí)有一個(gè)疑問(wèn),既然有了synchronized修飾方法的同步方式,為什么還需要synchronized修飾同步代碼塊的方式呢?而這個(gè)問(wèn)題也是synchronized的缺陷所在

synchronized的缺陷:當(dāng)某個(gè)線程進(jìn)入同步方法獲得對(duì)象鎖,那么其他線程訪問(wèn)這里對(duì)象的同步方法時(shí),必須等待或者阻塞,這對(duì)高并發(fā)的系統(tǒng)是致命的,這很容易導(dǎo)致系統(tǒng)的崩潰。如果某個(gè)線程在同步方法里面發(fā)生了死循環(huán),那么它就永遠(yuǎn)不會(huì)釋放這個(gè)對(duì)象鎖,那么其他線程就要永遠(yuǎn)的等待。這是一個(gè)致命的問(wèn)題。

當(dāng)然同步方法和同步代碼塊都會(huì)有這樣的缺陷,只要用了synchronized關(guān)鍵字就會(huì)有這樣的風(fēng)險(xiǎn)和缺陷。既然避免不了這種缺陷,那么就應(yīng)該將風(fēng)險(xiǎn)降到最低。這也是同步代碼塊在某種情況下要優(yōu)于同步方法的方面。例如在某個(gè)類(lèi)的方法里面:這個(gè)類(lèi)里面聲明了一個(gè)對(duì)象實(shí)例,SynObject so=new SynObject();在某個(gè)方法里面調(diào)用了這個(gè)實(shí)例的方法so.testsy();但是調(diào)用這個(gè)方法需要進(jìn)行同步,不能同時(shí)有多個(gè)線程同時(shí)執(zhí)行調(diào)用這個(gè)方法。

這時(shí)如果直接用synchronized修飾調(diào)用了so.testsy();代碼的方法,那么當(dāng)某個(gè)線程進(jìn)入了這個(gè)方法之后,這個(gè)對(duì)象其他同步方法都不能給其他線程訪問(wèn)了。假如這個(gè)方法需要執(zhí)行的時(shí)間很長(zhǎng),那么其他線程會(huì)一直阻塞,影響到系統(tǒng)的性能。

如果這時(shí)用synchronized來(lái)修飾代碼塊:synchronized(so){so.testsy();},那么這個(gè)方法加鎖的對(duì)象是so這個(gè)對(duì)象,跟執(zhí)行這行代碼的對(duì)象沒(méi)有關(guān)系,當(dāng)一個(gè)線程執(zhí)行這個(gè)方法時(shí),這對(duì)其他同步方法時(shí)沒(méi)有影響的,因?yàn)樗麄兂钟械逆i都完全不一樣。

不過(guò)這里還有一種特例,就是上面演示的第一個(gè)例子,對(duì)象鎖synchronized同時(shí)修飾方法和代碼塊,這時(shí)也可以體現(xiàn)到同步代碼塊的優(yōu)越性,如果test1方法同步代碼塊后面有非常多沒(méi)有同步的代碼,而且有一個(gè)100000的循環(huán),這導(dǎo)致test1方法會(huì)執(zhí)行時(shí)間非常長(zhǎng),那么如果直接用synchronized修飾方法,那么在方法沒(méi)執(zhí)行完之前,其他線程是不可以訪問(wèn)test2方法的,但是如果用了同步代碼塊,那么當(dāng)退出代碼塊時(shí)就已經(jīng)釋放了對(duì)象鎖,當(dāng)線程還在執(zhí)行test1的那個(gè)100000的循環(huán)時(shí),其他線程就已經(jīng)可以訪問(wèn)test2方法了。這就讓阻塞的機(jī)會(huì)或者線程更少。讓系統(tǒng)的性能更優(yōu)越。

一個(gè)類(lèi)的對(duì)象鎖和另一個(gè)類(lèi)的對(duì)象鎖是沒(méi)有關(guān)聯(lián)的,當(dāng)一個(gè)線程獲得A類(lèi)的對(duì)象鎖時(shí),它同時(shí)也可以獲得B類(lèi)的對(duì)象鎖。

可能上面只有理論和代碼,對(duì)剛接觸的人比較難理解,下面舉一個(gè)例子,

打個(gè)比方:一個(gè)object就像一個(gè)大房子,大門(mén)永遠(yuǎn)打開(kāi)。房子里有 很多房間(也就是方法)。

這些房間有上鎖的(synchronized方法), 和不上鎖之分(普通方法)。房門(mén)口放著一把鑰匙(key),這把鑰匙可以打開(kāi)所有上鎖的房間。

另外我把所有想調(diào)用該對(duì)象方法的線程比喻成想進(jìn)入這房子某個(gè) 房間的人。所有的東西就這么多了,下面我們看看這些東西之間如何作用的。

在此我們先來(lái)明確一下我們的前提條件。該對(duì)象至少有一個(gè)synchronized方法,否則這個(gè)key還有啥意義。當(dāng)然也就不會(huì)有我們的這個(gè)主題了。

一個(gè)人想進(jìn)入某間上了鎖的房間,他來(lái)到房子門(mén)口,看見(jiàn)鑰匙在那兒(說(shuō)明暫時(shí)還沒(méi)有其他人要使用上鎖的 房間)。于是他走上去拿到了鑰匙,并且按照自己 的計(jì)劃使用那些房間。注意一點(diǎn),他每次使用完一次上鎖的房間后會(huì)馬上把鑰匙還回去。即使他要連續(xù)使用兩間上鎖的房間,中間他也要把鑰匙還回去,再取回來(lái)。

因此,普通情況下鑰匙的使用原則是:“隨用隨借,用完即還。”

這時(shí)其他人可以不受限制的使用那些不上鎖的房間,一個(gè)人用一間可以,兩個(gè)人用一間也可以,沒(méi)限制。但是如果當(dāng)某個(gè)人想要進(jìn)入上鎖的房間,他就要跑到大門(mén)口去看看了。有鑰匙當(dāng)然拿了就走,沒(méi)有的話(huà),就只能等了。

要是很多人在等這把鑰匙,等鑰匙還回來(lái)以后,誰(shuí)會(huì)優(yōu)先得到鑰匙?Not guaranteed。象前面例子里那個(gè)想連續(xù)使用兩個(gè)上鎖房間的家伙,他中間還鑰匙的時(shí)候如果還有其他人在等鑰匙,那么沒(méi)有任何保證這家伙能再次拿到。 (JAVA規(guī)范在很多地方都明確說(shuō)明不保證,像Thread.sleep()休息后多久會(huì)返回運(yùn)行,相同優(yōu)先權(quán)的線程那個(gè)首先被執(zhí)行,當(dāng)要訪問(wèn)對(duì)象的鎖被 釋放后處于等待池的多個(gè)線程哪個(gè)會(huì)優(yōu)先得到,等等。我想最終的決定權(quán)是在JVM,之所以不保證,就是因?yàn)镴VM在做出上述決定的時(shí)候,絕不是簡(jiǎn)簡(jiǎn)單單根據(jù) 一個(gè)條件來(lái)做出判斷,而是根據(jù)很多條。而由于判斷條件太多,如果說(shuō)出來(lái)可能會(huì)影響JAVA的推廣,也可能是因?yàn)橹R(shí)產(chǎn)權(quán)保護(hù)的原因吧。SUN給了個(gè)不保證 就混過(guò)去了。無(wú)可厚非。但我相信這些不確定,并非完全不確定。因?yàn)橛?jì)算機(jī)這東西本身就是按指令運(yùn)行的。即使看起來(lái)很隨機(jī)的現(xiàn)象,其實(shí)都是有規(guī)律可尋。學(xué)過(guò) 計(jì)算機(jī)的都知道,計(jì)算機(jī)里隨機(jī)數(shù)的學(xué)名是偽隨機(jī)數(shù),是人運(yùn)用一定的方法寫(xiě)出來(lái)的,看上去隨機(jī)罷了。另外,或許是因?yàn)橐肱拇_太費(fèi)事,也沒(méi)多大意義,所 以不確定就不確定了吧。)

再來(lái)看看同步代碼塊。和同步方法有小小的不同。

1.從尺寸上講,同步代碼塊比同步方法小。你可以把同步代碼塊看成是沒(méi)上鎖房間里的一塊用帶鎖的屏風(fēng)隔開(kāi)的空間。

2.同步代碼塊還可以人為的指定獲得某個(gè)其它對(duì)象的key。就像是指定用哪一把鑰匙才能開(kāi)這個(gè)屏風(fēng)的鎖,你可以用本房的鑰匙;你也可以指定用另一個(gè)房子的鑰匙才能開(kāi),這樣的話(huà),你要跑到另一棟房子那兒把那個(gè)鑰匙拿來(lái),并用那個(gè)房子的鑰匙來(lái)打開(kāi)這個(gè)房子的帶鎖的屏風(fēng)。

以上就是Synchronized和Lock的區(qū)別有哪些,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI