<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【72期】面試官:對(duì)并發(fā)熟悉嗎?說(shuō)一下synchronized與Lock的區(qū)別與使用

          共 6798字,需瀏覽 14分鐘

           ·

          2020-10-28 15:37

          程序員的成長(zhǎng)之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
          關(guān)注


          閱讀本文大概需要 8 分鐘。

          來(lái)自:https://blog.csdn.net/u012403290/

          引言:

          昨天在學(xué)習(xí)別人分享的面試經(jīng)驗(yàn)時(shí),看到Lock的使用。想起自己在上次面試也遇到了synchronized與Lock的區(qū)別與使用。
          于是,我整理了兩者的區(qū)別和使用情況,同時(shí),對(duì)synchronized的使用過程一些常見問題的總結(jié),最后是參照源碼和說(shuō)明文檔,對(duì)Lock的使用寫了幾個(gè)簡(jiǎn)單的Demo。請(qǐng)大家批評(píng)指正。

          技術(shù)點(diǎn):

          1、線程與進(jìn)程:

          在開始之前先把進(jìn)程與線程進(jìn)行區(qū)分一下,一個(gè)程序最少需要一個(gè)進(jìn)程,而一個(gè)進(jìn)程最少需要一個(gè)線程。關(guān)系是線程–>進(jìn)程–>程序的大致組成結(jié)構(gòu)。所以線程是程序執(zhí)行流的最小單位,而進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。以下我們所有討論的都是建立在線程基礎(chǔ)之上。

          2、Thread的幾個(gè)重要方法:

          我們先了解一下Thread的幾個(gè)重要方法。
          • start()方法,調(diào)用該方法開始執(zhí)行該線程;

          • stop()方法,調(diào)用該方法強(qiáng)制結(jié)束該線程執(zhí)行;

          • join方法,調(diào)用該方法等待該線程結(jié)束。

          • sleep()方法,調(diào)用該方法該線程進(jìn)入等待。

          • run()方法,調(diào)用該方法直接執(zhí)行線程的run()方法,但是線程調(diào)用start()方法時(shí)也會(huì)運(yùn)行run()方法,區(qū)別就是一個(gè)是由線程調(diào)度運(yùn)行run()方法,一個(gè)是直接調(diào)用了線程中的run()方法!!

          看到這里,可能有些人就會(huì)問啦,那wait()和notify()呢?要注意,其實(shí)wait()與notify()方法是Object的方法,不是Thread的方法!!同時(shí),wait()與notify()會(huì)配合使用,分別表示線程掛起和線程恢復(fù)。
          這里還有一個(gè)很常見的問題,順帶提一下:wait()與sleep()的區(qū)別,簡(jiǎn)單來(lái)說(shuō)wait()會(huì)釋放對(duì)象鎖而sleep()不會(huì)釋放對(duì)象鎖。這些問題有很多的資料,不再贅述。

          3、線程狀態(tài):

          線程總共有5大狀態(tài),通過上面第二個(gè)知識(shí)點(diǎn)的介紹,理解起來(lái)就簡(jiǎn)單了。
          • 新建狀態(tài):新建線程對(duì)象,并沒有調(diào)用start()方法之前

          • 就緒狀態(tài):調(diào)用start()方法之后線程就進(jìn)入就緒狀態(tài),但是并不是說(shuō)只要調(diào)用start()方法線程就馬上變?yōu)楫?dāng)前線程,在變?yōu)楫?dāng)前線程之前都是為就緒狀態(tài)。值得一提的是,線程在睡眠和掛起中恢復(fù)的時(shí)候也會(huì)進(jìn)入就緒狀態(tài)哦。

          • 運(yùn)行狀態(tài):線程被設(shè)置為當(dāng)前線程,開始執(zhí)行run()方法。就是線程進(jìn)入運(yùn)行狀態(tài)

          • 阻塞狀態(tài):線程被暫停,比如說(shuō)調(diào)用sleep()方法后線程就進(jìn)入阻塞狀態(tài)

          • 死亡狀態(tài):線程執(zhí)行結(jié)束

          4、鎖類型

          • 可重入鎖:在執(zhí)行對(duì)象中所有同步方法不用再次獲得鎖

          • 可中斷鎖:在等待獲取鎖過程中可中斷

          • 公平鎖:按等待獲取鎖的線程的等待時(shí)間進(jìn)行獲取,等待時(shí)間長(zhǎng)的具有優(yōu)先獲取鎖權(quán)利

          • 讀寫鎖:對(duì)資源讀取和寫入的時(shí)候拆分為2部分處理,讀的時(shí)候可以多線程一起讀,寫的時(shí)候必須同步地寫

          synchronized與Lock的區(qū)別

          1、我把兩者的區(qū)別分類到了一個(gè)表中,方便大家對(duì)比:
          或許,看到這里還對(duì)LOCK所知甚少,那么接下來(lái),我們進(jìn)入LOCK的深入學(xué)習(xí)。

          Lock詳細(xì)介紹與Demo

          以下是Lock接口的源碼,筆者修剪之后的結(jié)果:
          public?interface?Lock?{

          ????/**
          ?????*?Acquires?the?lock.
          ?????*/

          ????void?lock();

          ????/**
          ?????*?Acquires?the?lock?unless?the?current?thread?is
          ?????*?{@linkplain?Thread#interrupt?interrupted}.
          ?????*/

          ????void?lockInterruptibly()?throws?InterruptedException;

          ????/**
          ?????*?Acquires?the?lock?only?if?it?is?free?at?the?time?of?invocation.
          ?????*/

          ????boolean?tryLock();

          ????/**
          ?????*?Acquires?the?lock?if?it?is?free?within?the?given?waiting?time?and?the
          ?????*?current?thread?has?not?been?{@linkplain?Thread#interrupt?interrupted}.
          ?????*/

          ????boolean?tryLock(long?time,?TimeUnit?unit)?throws?InterruptedException;

          ????/**
          ?????*?Releases?the?lock.
          ?????*/

          ????void?unlock();

          }
          從Lock接口中我們可以看到主要有個(gè)方法,這些方法的功能從注釋中可以看出:
          • lock():獲取鎖,如果鎖被暫用則一直等待

          • unlock():釋放鎖

          • tryLock(): 注意返回類型是boolean,如果獲取鎖的時(shí)候鎖被占用就返回false,否則返回true

          • tryLock(long time, TimeUnit unit):比起tryLock()就是給了一個(gè)時(shí)間期限,保證等待參數(shù)時(shí)間

          • lockInterruptibly():用該鎖的獲得方式,如果線程在獲取鎖的階段進(jìn)入了等待,那么可以中斷此線程,先去做別的事

          通過 以上的解釋,大致可以解釋在上個(gè)部分中“鎖類型(lockInterruptibly())”,“鎖狀態(tài)(tryLock())”等問題,還有就是前面子所獲取的過程我所寫的“大致就是可以嘗試獲得鎖,線程可以不會(huì)一直等待”用了“可以”的原因。
          下面是Lock一般使用的例子,注意ReentrantLock是Lock接口的實(shí)現(xiàn)。

          lock():

          package?com.brickworkers;

          import?java.util.concurrent.locks.Lock;
          import?java.util.concurrent.locks.ReentrantLock;

          public?class?LockTest?{
          ????private?Lock?lock?=?new?ReentrantLock();

          ????//需要參與同步的方法
          ????private?void?method(Thread?thread){
          ????????lock.lock();
          ????????try?{
          ????????????System.out.println("線程名"+thread.getName()?+?"獲得了鎖");
          ????????}catch(Exception?e){
          ????????????e.printStackTrace();
          ????????}?finally?{
          ????????????System.out.println("線程名"+thread.getName()?+?"釋放了鎖");
          ????????????lock.unlock();
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????LockTest?lockTest?=?new?LockTest();

          ????????//線程1
          ????????Thread?t1?=?new?Thread(new?Runnable()?{

          ????????????@Override
          ????????????public?void?run()?
          {
          ????????????????lockTest.method(Thread.currentThread());
          ????????????}
          ????????},?"t1");

          ????????Thread?t2?=?new?Thread(new?Runnable()?{

          ????????????@Override
          ????????????public?void?run()?
          {
          ????????????????lockTest.method(Thread.currentThread());
          ????????????}
          ????????},?"t2");

          ????????t1.start();
          ????????t2.start();
          ????}
          }
          //執(zhí)行情況:線程名t1獲得了鎖
          //?????????線程名t1釋放了鎖
          //?????????線程名t2獲得了鎖
          //?????????線程名t2釋放了鎖

          tryLock():

          package?com.brickworkers;

          import?java.util.concurrent.locks.Lock;
          import?java.util.concurrent.locks.ReentrantLock;

          public?class?LockTest?{
          ????private?Lock?lock?=?new?ReentrantLock();

          ????//需要參與同步的方法
          ????private?void?method(Thread?thread){
          /*????????lock.lock();
          ????????try?{
          ????????????System.out.println("線程名"+thread.getName()?+?"獲得了鎖");
          ????????}catch(Exception?e){
          ????????????e.printStackTrace();
          ????????}?finally?{
          ????????????System.out.println("線程名"+thread.getName()?+?"釋放了鎖");
          ????????????lock.unlock();
          ????????}*/



          ????????if(lock.tryLock()){
          ????????????try?{
          ????????????????System.out.println("線程名"+thread.getName()?+?"獲得了鎖");
          ????????????}catch(Exception?e){
          ????????????????e.printStackTrace();
          ????????????}?finally?{
          ????????????????System.out.println("線程名"+thread.getName()?+?"釋放了鎖");
          ????????????????lock.unlock();
          ????????????}
          ????????}else{
          ????????????System.out.println("我是"+Thread.currentThread().getName()+"有人占著鎖,我就不要啦");
          ????????}
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????LockTest?lockTest?=?new?LockTest();

          ????????//線程1
          ????????Thread?t1?=?new?Thread(new?Runnable()?{

          ????????????@Override
          ????????????public?void?run()?
          {
          ????????????????lockTest.method(Thread.currentThread());
          ????????????}
          ????????},?"t1");

          ????????Thread?t2?=?new?Thread(new?Runnable()?{

          ????????????@Override
          ????????????public?void?run()?
          {
          ????????????????lockTest.method(Thread.currentThread());
          ????????????}
          ????????},?"t2");

          ????????t1.start();
          ????????t2.start();
          ????}
          }

          //執(zhí)行結(jié)果:?線程名t2獲得了鎖
          //?????????我是t1有人占著鎖,我就不要啦
          //?????????線程名t2釋放了鎖
          看到這里相信大家也都會(huì)使用如何使用Lock了吧,關(guān)于tryLock(long time, TimeUnit unit)和lockInterruptibly()不再贅述。前者主要存在一個(gè)等待時(shí)間,在測(cè)試代碼中寫入一個(gè)等待時(shí)間,后者主要是等待中斷,會(huì)拋出一個(gè)中斷異常,常用度不高,喜歡探究可以自己深入研究。
          前面比較重提到“公平鎖”,在這里可以提一下ReentrantLock對(duì)于平衡鎖的定義,在源碼中有這么兩段:
          ?/**
          ?????*?Sync?object?for?non-fair?locks
          ?????*/

          ????static?final?class?NonfairSync?extends?Sync?{
          ????????private?static?final?long?serialVersionUID?=?7316153563782823691L;

          ????????/**
          ?????????*?Performs?lock.??Try?immediate?barge,?backing?up?to?normal
          ?????????*?acquire?on?failure.
          ?????????*/

          ????????final?void?lock()?{
          ????????????if?(compareAndSetState(0,?1))
          ????????????????setExclusiveOwnerThread(Thread.currentThread());
          ????????????else
          ????????????????acquire(1);
          ????????}

          ????????protected?final?boolean?tryAcquire(int?acquires)?{
          ????????????return?nonfairTryAcquire(acquires);
          ????????}
          ????}

          ????/**
          ?????*?Sync?object?for?fair?locks
          ?????*/

          ????static?final?class?FairSync?extends?Sync?{
          ????????private?static?final?long?serialVersionUID?=?-3000897897090466540L;

          ????????final?void?lock()?{
          ????????????acquire(1);
          ????????}

          ????????/**
          ?????????*?Fair?version?of?tryAcquire.??Don't?grant?access?unless
          ?????????*?recursive?call?or?no?waiters?or?is?first.
          ?????????*/

          ????????protected?final?boolean?tryAcquire(int?acquires)?{
          ????????????final?Thread?current?=?Thread.currentThread();
          ????????????int?c?=?getState();
          ????????????if?(c?==?0)?{
          ????????????????if?(!hasQueuedPredecessors()?&&
          ????????????????????compareAndSetState(0,?acquires))?{
          ????????????????????setExclusiveOwnerThread(current);
          ????????????????????return?true;
          ????????????????}
          ????????????}
          ????????????else?if?(current?==?getExclusiveOwnerThread())?{
          ????????????????int?nextc?=?c?+?acquires;
          ????????????????if?(nextc?0)
          ????????????????????throw?new?Error("Maximum?lock?count?exceeded");
          ????????????????setState(nextc);
          ????????????????return?true;
          ????????????}
          ????????????return?false;
          ????????}
          ????}
          從以上源碼可以看出在Lock中可以自己控制鎖是否公平,而且,默認(rèn)的是非公平鎖,以下是ReentrantLock的構(gòu)造函數(shù):
          ???public?ReentrantLock()?{
          ????????sync?=?new?NonfairSync();//默認(rèn)非公平鎖
          ????}

          尾記錄:

          筆者水平一般,不過此博客在引言中的目的已全部達(dá)到。這只是筆者在學(xué)習(xí)過程中的總結(jié)與概括,如存在不正確的,歡迎大家批評(píng)指出。

          補(bǔ)充

          1、兩種鎖的底層實(shí)現(xiàn)方式:

          synchronized:我們知道java是用字節(jié)碼指令來(lái)控制程序(這里不包括熱點(diǎn)代碼編譯成機(jī)器碼)。在字節(jié)指令中,存在有synchronized所包含的代碼塊,那么會(huì)形成2段流程的執(zhí)行。
          我們點(diǎn)擊查看SyncDemo.java的源碼SyncDemo.class,可以看到如下:
          如上就是這段代碼段字節(jié)碼指令,沒你想的那么難吧。言歸正傳,我們可以清晰段看到,其實(shí)synchronized映射成字節(jié)碼指令就是增加來(lái)兩個(gè)指令:monitorenter和monitorexit。當(dāng)一條線程進(jìn)行執(zhí)行的遇到monitorenter指令的時(shí)候,它會(huì)去嘗試獲得鎖,如果獲得鎖那么鎖計(jì)數(shù)+1(為什么會(huì)加一呢,因?yàn)樗且粋€(gè)可重入鎖,所以需要用這個(gè)鎖計(jì)數(shù)判斷鎖的情況),如果沒有獲得鎖,那么阻塞。當(dāng)它遇到monitorexit的時(shí)候,鎖計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器為0,那么就釋放鎖。
          那么有的朋友看到這里就疑惑了,那圖上有2個(gè)monitorexit呀?馬上回答這個(gè)問題:上面我以前寫的文章也有表述過,synchronized鎖釋放有兩種機(jī)制,一種就是執(zhí)行完釋放;另外一種就是發(fā)送異常,虛擬機(jī)釋放。圖中第二個(gè)monitorexit就是發(fā)生異常時(shí)執(zhí)行的流程,這就是我開頭說(shuō)的“會(huì)有2個(gè)流程存在“。而且,從圖中我們也可以看到在第13行,有一個(gè)goto指令,也就是說(shuō)如果正常運(yùn)行結(jié)束會(huì)跳轉(zhuǎn)到19行執(zhí)行。
          這下,你對(duì)synchronized是不是了解的很清晰了呢。接下來(lái)我們?cè)倭囊涣腖ock。
          Lock:Lock實(shí)現(xiàn)和synchronized不一樣,后者是一種悲觀鎖,它膽子很小,它很怕有人和它搶吃的,所以它每次吃東西前都把自己關(guān)起來(lái)。而Lock呢底層其實(shí)是CAS樂觀鎖的體現(xiàn),它無(wú)所謂,別人搶了它吃的,它重新去拿吃的就好啦,所以它很樂觀。具體底層怎么實(shí)現(xiàn),博主不在細(xì)述,有機(jī)會(huì)的話,我會(huì)對(duì)concurrent包下面的機(jī)制好好和大家說(shuō)說(shuō),如果面試問起,你就說(shuō)底層主要靠volatile和CAS操作實(shí)現(xiàn)的。
          現(xiàn)在,才是我真正想在這篇博文后面加的,我要說(shuō)的是:盡可能去使用synchronized而不要去使用LOCK
          什么概念呢?我和大家打個(gè)比方:你叫jdk,你生了一個(gè)孩子叫synchronized,后來(lái)呢,你領(lǐng)養(yǎng)了一個(gè)孩子叫LOCK。起初,LOCK剛來(lái)到新家的時(shí)候,它很乖,很懂事,各個(gè)方面都表現(xiàn)的比synchronized好。你很開心,但是你內(nèi)心深處又有一點(diǎn)淡淡的憂傷,你不希望你自己親生的孩子竟然還不如一個(gè)領(lǐng)養(yǎng)的孩子乖巧。這個(gè)時(shí)候,你對(duì)親生的孩子教育更加深刻了,你想證明,你的親生孩子synchronized并不會(huì)比領(lǐng)養(yǎng)的孩子LOCK差。(博主只是打個(gè)比方)

          那如何教育呢?

          在jdk1.6~jdk1.7的時(shí)候,也就是synchronized16、7歲的時(shí)候,你作為爸爸,你給他優(yōu)化了,具體優(yōu)化在哪里呢:

          1、線程自旋和適應(yīng)性自旋

          我們知道,java’線程其實(shí)是映射在內(nèi)核之上的,線程的掛起和恢復(fù)會(huì)極大的影響開銷。并且jdk官方人員發(fā)現(xiàn),很多線程在等待鎖的時(shí)候,在很短的一段時(shí)間就獲得了鎖,所以它們?cè)诰€程等待的時(shí)候,并不需要把線程掛起,而是讓他無(wú)目的的循環(huán),一般設(shè)置10次。這樣就避免了線程切換的開銷,極大的提升了性能。
          而適應(yīng)性自旋,是賦予了自旋一種學(xué)習(xí)能力,它并不固定自旋10次一下。他可以根據(jù)它前面線程的自旋情況,從而調(diào)整它的自旋,甚至是不經(jīng)過自旋而直接掛起。

          2、鎖消除

          什么叫鎖消除呢?就是把不必要的同步在編譯階段進(jìn)行移除。
          那么有的小伙伴又迷糊了,我自己寫的代碼我會(huì)不知道這里要不要加鎖?我加了鎖就是表示這邊會(huì)有同步呀?
          并不是這樣,這里所說(shuō)的鎖消除并不一定指代是你寫的代碼的鎖消除,我打一個(gè)比方:
          在jdk1.5以前,我們的String字符串拼接操作其實(shí)底層是StringBuffer來(lái)實(shí)現(xiàn)的(這個(gè)大家可以用我前面介紹的方法,寫一個(gè)簡(jiǎn)單的demo,然后查看class文件中的字節(jié)碼指令就清楚了),而在jdk1.5之后,那么是用StringBuilder來(lái)拼接的。我們考慮前面的情況,比如如下代碼:
          String?str1="qwe";
          String?str2="asd";
          String?str3=str1+str2;
          底層實(shí)現(xiàn)會(huì)變成這樣:
          StringBuffer?sb?=?new?StringBuffer();
          sb.append("qwe");
          sb.append("asd");
          我們知道,StringBuffer是一個(gè)線程安全的類,也就是說(shuō)兩個(gè)append方法都會(huì)同步,通過指針逃逸分析(就是變量不會(huì)外泄),我們發(fā)現(xiàn)在這段代碼并不存在線程安全問題,這個(gè)時(shí)候就會(huì)把這個(gè)同步鎖消除。

          3、鎖粗化

          在用synchronized的時(shí)候,我們都講究為了避免大開銷,盡量同步代碼塊要小。那么為什么還要加粗呢?
          我們繼續(xù)以上面的字符串拼接為例,我們知道在這一段代碼中,每一個(gè)append都需要同步一次,那么我可以把鎖粗化到第一個(gè)append和最后一個(gè)append(這里不要去糾結(jié)前面的鎖消除,我只是打個(gè)比方)

          4、輕量級(jí)鎖

          5、偏向鎖

          關(guān)于最后這兩種,我希望留個(gè)有緣的讀者自己去查找,我不希望我把一件事情描述的那么詳細(xì),自己動(dòng)手得到才是你自己的,博主可以告訴你的是,最后兩種并不難。。
          加油吧,各位!

          推薦閱讀:

          【71期】面試官:對(duì)并發(fā)熟悉嗎?談?wù)勀銓?duì)Java中常用的幾種線程池的理解

          【70期】面試官:對(duì)并發(fā)熟悉嗎?談?wù)剬?duì)volatile的使用及其原理

          【69期】面試官:對(duì)并發(fā)熟悉嗎?談?wù)劸€程間的協(xié)作(wait/notify/sleep/yield/join)

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號(hào)內(nèi)回復(fù)「2048」,即可免費(fèi)獲取!!

          微信掃描二維碼,關(guān)注我的公眾號(hào)

          朕已閱?

          瀏覽 29
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩影院久久 | 国产毛片毛片 | 不卡的自排偷排视频网站 | 欧美少妇久久久 | a在线免费播放 |