<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>

          說一下synchronized與Lock的區(qū)別與使用

          共 17963字,需瀏覽 36分鐘

           ·

          2021-07-20 12:15

          引言:

          昨天在學(xué)習(xí)別人分享的面試經(jīng)驗時,看到Lock的使用。想起自己在上次面試也遇到了synchronized與Lock的區(qū)別與使用。

          于是,我整理了兩者的區(qū)別和使用情況,同時,對synchronized的使用過程一些常見問題的總結(jié),最后是參照源碼和說明文檔,對Lock的使用寫了幾個簡單的Demo。請大家批評指正。

          技術(shù)點:

          1、線程與進程:

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

          2、Thread的幾個重要方法:

          我們先了解一下Thread的幾個重要方法。

          • start()方法,調(diào)用該方法開始執(zhí)行該線程;

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

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

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

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

          看到這里,可能有些人就會問啦,那wait()和notify()呢?要注意,其實wait()與notify()方法是Object的方法,不是Thread的方法??!同時,wait()與notify()會配合使用,分別表示線程掛起和線程恢復(fù)。

          這里還有一個很常見的問題,順帶提一下:wait()與sleep()的區(qū)別,簡單來說wait()會釋放對象鎖而sleep()不會釋放對象鎖。這些問題有很多的資料,不再贅述。

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

          線程總共有5大狀態(tài),通過上面第二個知識點的介紹,理解起來就簡單了。

          • 新建狀態(tài):新建線程對象,并沒有調(diào)用start()方法之前

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

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

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

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

          4、鎖類型

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

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

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

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

          synchronized與Lock的區(qū)別

          1、我把兩者的區(qū)別分類到了一個表中,方便大家對比:

          或許,看到這里還對LOCK所知甚少,那么接下來,我們進入LOCK的深入學(xué)習(xí)。

          Lock詳細介紹與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接口中我們可以看到主要有個方法,這些方法的功能從注釋中可以看出:

          • lock():獲取鎖,如果鎖被暫用則一直等待

          • unlock():釋放鎖

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

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

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

          通過 以上的解釋,大致可以解釋在上個部分中“鎖類型(lockInterruptibly())”,“鎖狀態(tài)(tryLock())”等問題,還有就是前面子所獲取的過程我所寫的“大致就是可以嘗試獲得鎖,線程可以不會一直等待”用了“可以”的原因。

          下面是Lock一般使用的例子,注意ReentrantLock是Lock接口的實現(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釋放了鎖

          看到這里相信大家也都會使用如何使用Lock了吧,關(guān)于tryLock(long time, TimeUnit unit)和lockInterruptibly()不再贅述。前者主要存在一個等待時間,在測試代碼中寫入一個等待時間,后者主要是等待中斷,會拋出一個中斷異常,常用度不高,喜歡探究可以自己深入研究。

          前面比較重提到“公平鎖”,在這里可以提一下ReentrantLock對于平衡鎖的定義,在源碼中有這么兩段:

           /**
               * 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(01))
                          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中可以自己控制鎖是否公平,而且,默認的是非公平鎖,以下是ReentrantLock的構(gòu)造函數(shù):

             public ReentrantLock() {
                  sync = new NonfairSync();//默認非公平鎖
              }

          尾記錄:

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

          補充

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

          synchronized:我們知道java是用字節(jié)碼指令來控制程序(這里不包括熱點代碼編譯成機器碼)。在字節(jié)指令中,存在有synchronized所包含的代碼塊,那么會形成2段流程的執(zhí)行。

          我們點擊查看SyncDemo.java的源碼SyncDemo.class,可以看到如下:

          如上就是這段代碼段字節(jié)碼指令,沒你想的那么難吧。言歸正傳,我們可以清晰段看到,其實synchronized映射成字節(jié)碼指令就是增加來兩個指令:monitorenter和monitorexit。當一條線程進行執(zhí)行的遇到monitorenter指令的時候,它會去嘗試獲得鎖,如果獲得鎖那么鎖計數(shù)+1(為什么會加一呢,因為它是一個可重入鎖,所以需要用這個鎖計數(shù)判斷鎖的情況),如果沒有獲得鎖,那么阻塞。當它遇到monitorexit的時候,鎖計數(shù)器-1,當計數(shù)器為0,那么就釋放鎖。

          那么有的朋友看到這里就疑惑了,那圖上有2個monitorexit呀?馬上回答這個問題:上面我以前寫的文章也有表述過,synchronized鎖釋放有兩種機制,一種就是執(zhí)行完釋放;另外一種就是發(fā)送異常,虛擬機釋放。圖中第二個monitorexit就是發(fā)生異常時執(zhí)行的流程,這就是我開頭說的“會有2個流程存在“。而且,從圖中我們也可以看到在第13行,有一個goto指令,也就是說如果正常運行結(jié)束會跳轉(zhuǎn)到19行執(zhí)行。

          這下,你對synchronized是不是了解的很清晰了呢。接下來我們再聊一聊Lock。

          Lock:Lock實現(xiàn)和synchronized不一樣,后者是一種悲觀鎖,它膽子很小,它很怕有人和它搶吃的,所以它每次吃東西前都把自己關(guān)起來。而Lock呢底層其實是CAS樂觀鎖的體現(xiàn),它無所謂,別人搶了它吃的,它重新去拿吃的就好啦,所以它很樂觀。具體底層怎么實現(xiàn),博主不在細述,有機會的話,我會對concurrent包下面的機制好好和大家說說,如果面試問起,你就說底層主要靠volatile和CAS操作實現(xiàn)的。

          現(xiàn)在,才是我真正想在這篇博文后面加的,我要說的是:盡可能去使用synchronized而不要去使用LOCK

          什么概念呢?我和大家打個比方:你叫jdk,你生了一個孩子叫synchronized,后來呢,你領(lǐng)養(yǎng)了一個孩子叫LOCK。起初,LOCK剛來到新家的時候,它很乖,很懂事,各個方面都表現(xiàn)的比synchronized好。你很開心,但是你內(nèi)心深處又有一點淡淡的憂傷,你不希望你自己親生的孩子竟然還不如一個領(lǐng)養(yǎng)的孩子乖巧。這個時候,你對親生的孩子教育更加深刻了,你想證明,你的親生孩子synchronized并不會比領(lǐng)養(yǎng)的孩子LOCK差。(博主只是打個比方)

          那如何教育呢?

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

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

          我們知道,java’線程其實是映射在內(nèi)核之上的,線程的掛起和恢復(fù)會極大的影響開銷。并且jdk官方人員發(fā)現(xiàn),很多線程在等待鎖的時候,在很短的一段時間就獲得了鎖,所以它們在線程等待的時候,并不需要把線程掛起,而是讓他無目的的循環(huán),一般設(shè)置10次。這樣就避免了線程切換的開銷,極大的提升了性能。

          而適應(yīng)性自旋,是賦予了自旋一種學(xué)習(xí)能力,它并不固定自旋10次一下。他可以根據(jù)它前面線程的自旋情況,從而調(diào)整它的自旋,甚至是不經(jīng)過自旋而直接掛起。

          2、鎖消除

          什么叫鎖消除呢?就是把不必要的同步在編譯階段進行移除。

          那么有的小伙伴又迷糊了,我自己寫的代碼我會不知道這里要不要加鎖?我加了鎖就是表示這邊會有同步呀?

          并不是這樣,這里所說的鎖消除并不一定指代是你寫的代碼的鎖消除,我打一個比方:

          在jdk1.5以前,我們的String字符串拼接操作其實底層是StringBuffer來實現(xiàn)的(這個大家可以用我前面介紹的方法,寫一個簡單的demo,然后查看class文件中的字節(jié)碼指令就清楚了),而在jdk1.5之后,那么是用StringBuilder來拼接的。我們考慮前面的情況,比如如下代碼:

          String str1="qwe";
          String str2="asd";
          String str3=str1+str2;

          底層實現(xiàn)會變成這樣:

          StringBuffer sb = new StringBuffer();
          sb.append("qwe");
          sb.append("asd");

          我們知道,StringBuffer是一個線程安全的類,也就是說兩個append方法都會同步,通過指針逃逸分析(就是變量不會外泄),我們發(fā)現(xiàn)在這段代碼并不存在線程安全問題,這個時候就會把這個同步鎖消除。

          3、鎖粗化

          在用synchronized的時候,我們都講究為了避免大開銷,盡量同步代碼塊要小。那么為什么還要加粗呢?

          我們繼續(xù)以上面的字符串拼接為例,我們知道在這一段代碼中,每一個append都需要同步一次,那么我可以把鎖粗化到第一個append和最后一個append(這里不要去糾結(jié)前面的鎖消除,我只是打個比方)

          4、輕量級鎖

          5、偏向鎖

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

          來源:https://blog.csdn.net/u012403290/


          分享一下我寫的《10萬字Springboot經(jīng)典學(xué)習(xí)筆記》中,點擊下面小卡片,進入【Java禿頭哥】,回復(fù):筆記,即可免費獲取。

          點贊是最大的支持 

          瀏覽 30
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美一区二区三区四还视频 | 波多野结衣av中文字幕 | 中文字慕一色哟哟 | 日韩欧美在线观看视频 | 国产精品久久久久久久久借妻 |