欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程中Lock鎖的使用總結(jié)

 更新時(shí)間:2020年08月03日 10:22:30   作者:fancyerII  
這篇文章主要介紹了Java多線程中Lock鎖的使用總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

多核時(shí)代

      摩爾定律告訴我們:當(dāng)價(jià)格不變時(shí),集成電路上可容納的晶體管數(shù)目,約每隔18個(gè)月便會(huì)增加一倍,性能也將提升一倍。換言之,每一美元所能買(mǎi)到的電腦性能,將每隔18個(gè)月翻兩倍以上。然而最近摩爾定律似乎遇到了麻煩,目前微處理器的集成度似乎到了極限,在目前的制造工藝和體系架構(gòu)下很難再提高單個(gè)處理器的速度了,否則它就被燒壞了。所以現(xiàn)在的芯片制造商改變了策略,轉(zhuǎn)而在一個(gè)電路板上集成更多的處理器,也就是我們現(xiàn)在常見(jiàn)的多核處理器。

      這就給軟件行業(yè)帶來(lái)麻煩(也可以說(shuō)帶來(lái)機(jī)會(huì),比如說(shuō)就業(yè)機(jī)會(huì),呵呵)。原來(lái)的情況是:我買(mǎi)一臺(tái)頻率比原來(lái)快一倍的處理器,那么我的程序就比原來(lái)快一倍,軟件工程師什么也不用干?,F(xiàn)在不一樣了,我買(mǎi)一臺(tái)雙核的處理器,我的程序和原來(lái)一樣慢,當(dāng)然這條機(jī)器同時(shí)處理的任務(wù)可以變多了,但是對(duì)于單個(gè)任務(wù)來(lái)說(shuō)并沒(méi)有幫助。

      在幾年前,并發(fā)(Concurrent)和并行(Paralleling)程序設(shè)計(jì)還是在少量的地方使用,現(xiàn)在在個(gè)人的PC機(jī)上已經(jīng)是很常見(jiàn)了。(Concurrency and parallelism的區(qū)別參考 這個(gè)帖子

      造個(gè)諸葛亮的價(jià)錢(qián)遠(yuǎn)遠(yuǎn)高于造三個(gè)臭皮匠!多核是在一臺(tái)機(jī)器上的并發(fā),但是單機(jī)也是會(huì)到極限,所以分布式的計(jì)算也是類(lèi)似的思路,用大量普通的機(jī)器協(xié)作完成一項(xiàng)任務(wù)。

      但是要想編寫(xiě)一個(gè)正確并且高效的能利用多核的多線程程序不是件容易的是,更別說(shuō)分布式的情況(網(wǎng)絡(luò)問(wèn)題,機(jī)器故障,負(fù)載均衡,。。。)?,F(xiàn)在的編譯器沒(méi)有辦法把單線程的程序自動(dòng)編譯成一個(gè)多線程的版本(如果到了那一天,估計(jì)所有的程序員就失業(yè)了)。所以只能提供一些語(yǔ)言上的支持(比如scala/erlang)或者mapreduce這樣的框架。

      Java雖然沒(méi)有提供scala那樣的基于消息的模型,但是也提供了豐富的concurrent特性,并且屏蔽了平臺(tái)的相關(guān)性(這不是件容易的事,比如多個(gè)處理器有自己的緩存,他們寫(xiě)的東西不會(huì)離開(kāi)被其它處理器看到),下面我們看看java的內(nèi)存模型(JMM)

JMM(Java Memory Model)

     并行程序有很多模型,比如共享內(nèi)存模型,消息傳遞模型等等。這些模型或多或少的利用了平臺(tái)相關(guān)的特性(在并行程序設(shè)計(jì)里很難回避平臺(tái)的特性以便高效的通信),Java抽象出了自己的內(nèi)存模型,使得開(kāi)放人員看不到平臺(tái)的差異(這不是件容易的事),不過(guò)即使這樣,和傳統(tǒng)程序不同,我們還是不能完全不了解一些體系架構(gòu)的細(xì)節(jié)問(wèn)題,至少我們得了解一些。

     在共享內(nèi)存的多處理器體系架構(gòu)里(我們現(xiàn)在用的服務(wù)器甚至筆記本都是),每個(gè)處理器都有自己的局部緩存并定期的使之與內(nèi)存同步。不同的處理器架構(gòu)保證了不同程度的緩存一致性(cache coherence),所以操作系統(tǒng),編譯器和運(yùn)行時(shí)環(huán)境必須一起努力來(lái)彌補(bǔ)平臺(tái)的差異性。

      讓每個(gè)處理器都知道其它處理器的狀態(tài)的代價(jià)是非常昂貴的,所以大多數(shù)架構(gòu)都不會(huì)保證一致性,這通常不會(huì)有什么問(wèn)題:進(jìn)程/線程直接并不共享信息,編譯器可以調(diào)整代碼執(zhí)行順序以便提高效率,我們都很開(kāi)心。當(dāng)然也有需要在線程之間進(jìn)行同步的時(shí)候,比如某個(gè)線程要讀取到另一個(gè)線程寫(xiě)入的信息,這個(gè)時(shí)候緩存里的數(shù)據(jù)就得同步到內(nèi)存里才行。所以這些體系架構(gòu)都提供了一些指令來(lái)完成數(shù)據(jù)的同步(當(dāng)然這些指令是非常費(fèi)時(shí)的,能不做就盡量不做)。這些指令一般叫做memory barriers or fences。當(dāng)然只是很底層的一些東西,所幸Java提供了一些高層的抽象,讓我們的生活變得容易一些。

     sequential consistency: 我們假設(shè)一個(gè)線程執(zhí)行(可能在多個(gè)處理器上切換),每個(gè)變量讀取到的值都是最新的修改(也就是Cache里的立馬生效),這樣得到的結(jié)果是我們預(yù)期的。

     但是讓我們意外的事情是:如果我們不做任何事情,那么很可能會(huì)出現(xiàn)錯(cuò)誤,比如下面的這個(gè)例子:     

public class NoVisibility {
  private static boolean ready;
  private static int number;
 
  private static class ReaderThread extends Thread {
    public void run() {
      while (!ready)
        Thread.yield();
      System.out.println(number);
    }
  }
 
  public static void main(String[] args) {
    new ReaderThread().start();
    number = 42;
    ready = true;
  }
}

     我們?cè)谥骶€程里先讓number=42(初始值是0),然后讓ready=true,而另一個(gè)線程不斷堅(jiān)持是否ready,如果ready,那么讀出number。很自然的我們期望子線程打印出42,但是很可能結(jié)果會(huì)另我們失望。編譯器可能會(huì)調(diào)換number=42  和  ready=true的順序(思考一下為什么它要這么干?為什么在單線程的情況下沒(méi)有問(wèn)題?),另外子線程可能永遠(yuǎn)在while里死循環(huán)。為什么?子線程會(huì)永遠(yuǎn)看不到ready的變化?這也許讓很多人吃驚,事實(shí)確實(shí)如此,JSR并不保證這一點(diǎn)(雖然大多數(shù)時(shí)候子線程能夠退出),參考這個(gè)帖子JMM的文章

vilatile和snychronized(intrinsic Lock)

     vilatile關(guān)鍵字告訴編譯器,一個(gè)線程對(duì)某個(gè)變量的修改立即對(duì)所有其它線程看見(jiàn),加上這個(gè)能保證上面的程序不會(huì)死循環(huán)。但是不能保證讀到42,也就是保證number=42和ready=true的執(zhí)行順序,要保證這點(diǎn)就要用到synchronized。

     synchronized能夠保證執(zhí)行的順序,除此之外,它也能保證可見(jiàn)性。

public class NoVisibility {
  private static boolean ready;
  private static int number;
 
  private static class ReaderThread extends Thread {
    public void run() {
      boolean r=false;
      while (true){
        synchronized(NoVisibility.class){
          r=ready;
        }
        if(r) break;
        else Thread.yield();
      }   
      System.out.println(number);
    }
  }
 
  public static void main(String[] args) {
    new ReaderThread().start();
    synchronized(NoVisibility.class){
      number = 42;
      ready = true;
    }
  }
}
synchronized(NoVisibility.class){
    number = 42;
    ready = true;
 }

這段代碼保證了兩個(gè)語(yǔ)句的執(zhí)行順序

synchronized(NoVisibility.class){
    r=ready;     
}

這保證子線程能看到ready的變化 注意他們必須synchronized同一個(gè)對(duì)象,如果是下面的代碼,則不能有任何保障。為什么?試想任何synchronized里的變量必須立即對(duì)所有的可見(jiàn),那么代價(jià)太大, 比如我有這樣的需求:我只要求兩個(gè)語(yǔ)句順序執(zhí)行,它是否對(duì)別人可見(jiàn)我并不關(guān)心。

synchronized(AnotherObject){
    r=ready;     
}

每個(gè)對(duì)象都有個(gè)Monitor,所以synchronized也經(jīng)常叫Monitor Lock,另外這個(gè)鎖是語(yǔ)言內(nèi)置的,所以也叫Intrinsic Lock。 這兩個(gè)關(guān)鍵字是java1.5之前就有了,在java1.5之后新引進(jìn)了java.util.concurrent包,這里有我們需要關(guān)注的很多東西,這里我們只關(guān)心Lock相關(guān)的接口和類(lèi)。 不過(guò)synchronized來(lái)解決互斥不是很完美嗎?我為什么要花力氣搞這些新鮮東西呢?下面我們來(lái)看看synchronized解決不了(或者很難解決)的問(wèn)題

銀行轉(zhuǎn)賬的例子

// Warning: deadlock-prone!
public void transferMoney(Account fromAccount,
             Account toAccount,
             DollarAmount amount)
    throws InsufficientFundsException {
  synchronized (fromAccount) {
    synchronized (toAccount) {
      if (fromAccount.getBalance().compareTo(amount) < 0)
        throw new InsufficientFundsException();
      else {
        fromAccount.debit(amount);
        toAccount.credit(amount);
      }
    }
  }
}

比如我要在兩個(gè)用戶之間轉(zhuǎn)賬,為了防止意外,我必須同時(shí)鎖定兩個(gè)賬戶。但是這可能造成死鎖。比如:

A: transferMoney(myAccount, yourAccount, 10);
B: transferMoney(yourAccount, myAccount, 20);

當(dāng)線程A鎖住myAccount時(shí),B鎖住了toAccount,這個(gè)時(shí)候A嘗試鎖住toAccount,但是已經(jīng)被B鎖住,所以A不能繼續(xù)運(yùn)行,同理B也不能運(yùn)行,造成死鎖。

怎么解決呢?你也許回想,我先鎖住一個(gè)賬戶,然后"嘗試"鎖定另一個(gè)賬戶,如果“失敗”,那么我釋放所有的鎖,“休息”一下再繼續(xù)嘗試,當(dāng)然兩個(gè)線程節(jié)拍一致的話,可能造成“活鎖”

可惜synchronized不能提供這樣的語(yǔ)義,它一旦嘗試加鎖,只能拿到鎖,你不能控制它,比如你可能有這樣的需求:嘗試拿鎖30s,如果拿不到就算了,synchronized是沒(méi)辦法滿足這樣的需求的。另外你使用“鴕鳥(niǎo)”策略來(lái)解決死鎖:什么也不干,如果死鎖了,kill他們,重啟他們。這種策略看起來(lái)很瘋狂,不過(guò)如果死鎖的概率很多,而避免死鎖的算法很復(fù)雜,那這也是可以一試的策略(那一堆死鎖發(fā)生的充分必要條件太麻煩了?。。。?。下面我們仔細(xì)的來(lái)看看java1.5后提供的Lock接口及其相關(guān)類(lèi)。

Lock接口

    Lock的基本用法如下,為了防止異常退出時(shí)沒(méi)有釋放鎖,一般都在拿到鎖后立馬try,try住所有臨界區(qū)的代碼,然后finally釋放鎖。

    主要和synchronized的區(qū)別,synchronized里我們不用操心這些,如果synchronized保護(hù)的代碼拋出異常,那么jvm會(huì)釋放掉Monitor Lock。

   Lock l = ...
   l.lock();
   try {
     // access the resource protected by this lock
   } finally {
     l.unlock();
   }

  Lock.lock()在鎖定成功后釋放鎖之前,它所保護(hù)的代碼段必須與使用synchronized保護(hù)的代碼段有相同的語(yǔ)義(可見(jiàn)性,順序性)。

  所以從這個(gè)角度來(lái)說(shuō),Lock完全可以代替synchronized,那么是否應(yīng)該拋棄掉synchronized呢?答案是否定的。

  是否應(yīng)該拋棄synchronized?

   在java5引進(jìn)Lock后,實(shí)現(xiàn)了Lock接口的類(lèi)就是ReentrantLock(呆會(huì)再解釋Reentrant),因?yàn)閖ava5之前synchronized的實(shí)現(xiàn)很爛,同樣是為了實(shí)現(xiàn)互斥,ReentrantLock會(huì)比synchronized速度上快很多,不過(guò)到了jdk6之后就不是這樣了,下面是一個(gè)測(cè)試結(jié)果:  from book "Java Concurrency in Practice"
  橫軸是線程數(shù),縱軸是ReentrantLock的吞吐量/IntrinsicLock的吞吐量。

  可以看出,jdk5中,ReentrantLock快很多,但是到了jdk6,他們就沒(méi)什么大的差別了。

  synchronized的優(yōu)點(diǎn):鎖的釋放是語(yǔ)言內(nèi)置的,不會(huì)出現(xiàn)忘記釋放鎖的情況,另外由于是語(yǔ)言內(nèi)置的支持,調(diào)試是能很快知道鎖被哪個(gè)線程持有,它加鎖的次數(shù)。而Lock只是util.concurrent一個(gè)普通的類(lèi),所以調(diào)試器并不知道這個(gè)鎖的任何信息,它只是一個(gè)普通的對(duì)象(當(dāng)然你可以仔細(xì)觀察每個(gè)線程的stack frame來(lái)看它在等待鎖)。

  所以建議:如果只是為了實(shí)現(xiàn)互斥,那么使用synchronized(扔掉jdk5吧,現(xiàn)在都java7了),如果想用Lock附加的功能,那么才使用Lock。

  下面回來(lái)繼續(xù)看Lock接口。  

Interface Lock

public interface Lock {
  void lock();
  void lockInterruptibly() throws InterruptedException;
  boolean tryLock();
  boolean tryLock(long timeout, TimeUnit unit)
    throws InterruptedException;
  void unlock();
  Condition newCondition();
}

  void lock();   

      嘗試獲取鎖。如果鎖被別人拿著,那么當(dāng)前線程不在執(zhí)行,也不能被調(diào)度,直到拿到鎖為止。

  void lockInterruptibly() throws InterruptedException

      嘗試獲取鎖,除非被interrupted。如果鎖可以獲取,那么立刻返回。

      如果無(wú)非獲取鎖,那么線程停止執(zhí)行,并且不能被再調(diào)度,直到:

  •   當(dāng)前線程獲得鎖
  •   如果鎖的實(shí)現(xiàn)支持interruption,并且有其它線程interrupt當(dāng)前線程。

      仔細(xì)閱讀javadoc的第二個(gè)情況:Lock接口并不要求Lock的實(shí)現(xiàn)支持interruption,不過(guò)sun jdk的實(shí)現(xiàn)都是支持的。
      這個(gè)函數(shù)在下面兩個(gè)情況下拋出InterruptedException:

  •   如果鎖的實(shí)現(xiàn)支持interruption,并且有其它線程interrupt當(dāng)前線程。
  •   線程調(diào)用這個(gè)函數(shù)之前就被設(shè)置了interrupted狀態(tài)位

      可以發(fā)現(xiàn)這個(gè)方法并不區(qū)分這個(gè)interrupted狀態(tài)位是之前就有的還是lock過(guò)程中產(chǎn)生的。不管如果,拋出異常后會(huì)清除interrupted標(biāo)記。

      使用這個(gè)方法,我們可以中斷某個(gè)等鎖的線程,比如我們檢測(cè)到了死鎖,那么我們可以中斷這個(gè)線程      

 boolean tryLock()

       嘗試獲取鎖,如果可以,那么鎖住對(duì)象然后返回true,否則返回false,不管怎么樣,這個(gè)方法會(huì)立即返回。下面的例子展示了用這個(gè)方法來(lái)解決前面轉(zhuǎn)賬的死鎖:

public boolean transferMoney(Account fromAcct,
               Account toAcct,
               DollarAmount amount,
               long timeout,
               TimeUnit unit)
    throws InsufficientFundsException, InterruptedException {
  long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
  long randMod = getRandomDelayModulusNanos(timeout, unit);
  long stopTime = System.nanoTime() + unit.toNanos(timeout);
 
  while (true) {
    if (fromAcct.lock.tryLock()) {
      try {
        if (toAcct.lock.tryLock()) {
          try {
            if (fromAcct.getBalance().compareTo(amount)
                < 0)
              throw new InsufficientFundsException();
            else {
              fromAcct.debit(amount);
              toAcct.credit(amount);
              return true;
            }
          } finally {
            toAcct.lock.unlock();
          }
         }
       } finally {
         fromAcct.lock.unlock();
       }
     }
     if (System.nanoTime() < stopTime)
       return false;
     NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
   }
}

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

    和tryLock類(lèi)似,不過(guò)不是立即返回,而是嘗試一定時(shí)間后還拿不到鎖就返回

unlock

    釋放鎖

newCondition

     暫且不管

Class ReentrantLock

     這是sun jdk(open jdk)里唯一直接實(shí)現(xiàn)了Lock接口的類(lèi),所以如果你想用Lock的那些特性,比如tryLock,那么就應(yīng)該首先考慮它

     首先我們解釋一下Reentrant

      Reentrant翻譯成中文應(yīng)該是“可重入”,對(duì)于鎖來(lái)說(shuō),可重入是指如果一個(gè)線程已拿到過(guò)一把鎖,那么它可以再次拿到鎖。

     聽(tīng)起來(lái)似乎沒(méi)有什么意思,讓我們來(lái)看看“不可重入”鎖可能的一些問(wèn)題和需要使用”可重入“鎖的場(chǎng)景吧。

public class Widget {
  public synchronized void doSomething() {
    ...
  }
}
 
public class LoggingWidget extends Widget {
  public synchronized void doSomething() {
    System.out.println(toString() + ": calling doSomething");
    super.doSomething();
  }
}
 
 
 
Widget widget=new LoggingWidget();
 
widget.doSomething();

      設(shè)想這樣一個(gè)應(yīng)用場(chǎng)景:我們有一個(gè)圖的數(shù)據(jù)結(jié)構(gòu),我們需要遍歷所有節(jié)點(diǎn),找到滿足某些條件的節(jié)點(diǎn),鎖定所有這些節(jié)點(diǎn),然后對(duì)他們進(jìn)行一些操作。由于圖的遍歷可能重復(fù)訪問(wèn)某個(gè)節(jié)點(diǎn),如果簡(jiǎn)單的鎖定每個(gè)滿足條件的節(jié)點(diǎn),那么可能死鎖。當(dāng)然我們可以自己用程序記下哪些節(jié)點(diǎn)已經(jīng)訪問(wèn)過(guò)了,不過(guò)也可以把這就事情交給ReentrantLock,第二次鎖定某個(gè)對(duì)象也會(huì)成功并立即返回。那么你可能會(huì)問(wèn),我釋放鎖的時(shí)候怎么記得它鎖定過(guò)了多少次呢?如果釋放少了,那么會(huì)死鎖;釋放多了,可能也會(huì)有問(wèn)題(有些鎖實(shí)現(xiàn)會(huì)拋出異常,但是JMM好像沒(méi)有定義)。

      【上面的場(chǎng)景參考http://stackoverflow.com/questions/1312259/what-is-the-re-entrant-lock-and-concept-in-general】       不用擔(dān)心,ReentrantLock提供了getHoldCount方法,最后釋放這么多次就好了。

       ReentrantLock會(huì)記下當(dāng)前拿鎖的線程,已經(jīng)拿鎖的次數(shù),每次unlock都會(huì)減一,如果為零了,那么釋放鎖,另一個(gè)線程到鎖并且計(jì)數(shù)器值為一。

      ReentrantLock的構(gòu)造函數(shù)可以接受一個(gè)fairness的參數(shù)。如果為true,那么它會(huì)傾向于把鎖給等待時(shí)間最長(zhǎng)的線程。但是這樣的代價(jià)也是巨大的:                          
             橫軸是并發(fā)線程數(shù),參考方法是ConcurrentHashMap,另外分別用Nonfair Lock和 fair Lock封裝普通的HashMap,可以看到,是否fair的差別是非常巨大的。
           正如前面所說(shuō)的,ReentrantLock是支持Interrupted的。

Interface ReadWriteLock

        有的應(yīng)用場(chǎng)景下,有兩類(lèi)角色:Reader和Writer。Reader讀取數(shù)據(jù),Writer更新數(shù)據(jù)。多個(gè)Reader同時(shí)讀取是沒(méi)有問(wèn)題的,但是Reader們和Writer是互斥的,并且Writer和Writer也是互斥的。而且很多應(yīng)用中,Reader會(huì)很多,而Writer會(huì)比較少。這個(gè)接口就是為了解決這類(lèi)特殊場(chǎng)景的。

public interface ReadWriteLock {
  Lock readLock();
  Lock writeLock();
}
 
用法:
ReadWriteLock rwl = ...;
//Reader threads
read(){
  rwl.readLock().lock();
  try{
    //entering critical setion
  }finally{
    rwl.readLock().unlock();
  }
}
write(){  rwl.writeLock().lock();  try{    //entering critical setion  }finally{    rwl.writeLock().unlock();  }}
 

 Class ReentrantReadWriteLock

        這是Sun jdk里唯一實(shí)現(xiàn)ReadWriteLock接口的類(lèi)。         這個(gè)類(lèi)的特性:

         獲取鎖的順序

                 這個(gè)類(lèi)并不傾向Reader或者Writer,不過(guò)有個(gè)fairness的策略         非公平模式(默認(rèn))

                如果很多Reader和Writer的話,很可能Reder一直能獲取鎖,而Writer可能會(huì)饑餓

            公平模式

                這種模式下,會(huì)盡量以請(qǐng)求鎖的順序來(lái)保證公平性。當(dāng)前鎖釋放以后,等待時(shí)間最長(zhǎng)的Writer或者一組Reader(Reader是一伙的?。┇@取鎖。                 如果鎖被拿著,這時(shí)Writer來(lái)了,他會(huì)開(kāi)始排隊(duì);如果Reader來(lái)了,如果它之前沒(méi)有Writer并且當(dāng)前拿鎖的是Reader,那么它直接就拿到鎖,當(dāng)然如果是Writer拿著,那么它也只能排  隊(duì)等鎖。 不過(guò)如果Reader拿著鎖,Writer排隊(duì),然后Reader排在Writer后,但是Writer放棄了排隊(duì)(比如它用的是tryLock 30s),那么Reader直接拿到鎖而不用排隊(duì)。

                還有就是ReentrantReadWriteLock.ReadLock.tryLock() 和 ReentrantReadWriteLock.WriteLock.tryLock()方法不管這些,一旦調(diào)用的時(shí)候能拿到鎖,那么它們就會(huì)插隊(duì)??!          

  Reentrancy

                從名字就知道它支持可重入。                 

                以前拿過(guò)鎖的Reader和Writer可以繼續(xù)拿鎖。另外拿到WriteLock的線程可以拿到ReadLock,但是反之不然。

            Lock downgrading

                 拿到WriteLock的可以直接變成ReadLock,不用釋放WriteLock再?gòu)男抡?qǐng)求ReadLock(這樣需要重新排隊(duì)),實(shí)現(xiàn)的方法是先拿到WriteLock,接著拿ReadLock(上面的特性保證了不會(huì)死鎖),然后釋放WriteLock,這樣就得到一個(gè)ReadLock并立馬持有。

           Interruption of lock acquisition

                 支持

      一個(gè)使用讀寫(xiě)鎖的例子                        

class CachedData {
  Object data;
  volatile boolean cacheValid;
  ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 
  void processCachedData() {
   rwl.readLock().lock();
   if (!cacheValid) {
    // Must release read lock before acquiring write lock
    rwl.readLock().unlock();
    rwl.writeLock().lock();
    // Recheck state because another thread might have acquired
    //  write lock and changed state before we did.
    if (!cacheValid) {
     data = ...
     cacheValid = true;
    }
    // Downgrade by acquiring read lock before releasing write lock
    rwl.readLock().lock();
    rwl.writeLock().unlock(); // Unlock write, still hold read
   }
 
   use(data);
   rwl.readLock().unlock();
  }
 }

  一個(gè)Cache數(shù)據(jù)的例子,讀取數(shù)據(jù)時(shí)首先拿讀鎖,如果cache是有效的(volatile boolean cacheValid),直接使用數(shù)據(jù)。

   如果失效了,那么釋放讀鎖,獲取寫(xiě)鎖【這個(gè)類(lèi)不支持upgrading】,然后double check一下是否cache有效,如果還是無(wú)效(說(shuō)明它應(yīng)該更新),那么更新數(shù)據(jù),并且修改變量cacheValid,讓其它線程看到。

   臭名昭著的double check

    前面提到了double check,這里也順便討論一下:

@NotThreadSafe
public class DoubleCheckedLocking {
  private static Resource resource;
 
  public static Resource getInstance() {
    if (resource == null) {
      synchronized (DoubleCheckedLocking.class) {
        if (resource == null)
          resource = new Resource();
      }
    }
    return resource;
  }
}

很多“hacker”再提到延遲加載的時(shí)候都會(huì)提到它,上面的代碼看起來(lái)沒(méi)有什么問(wèn)題:首先檢查一些resource,如果為空,那么加鎖,因?yàn)闄z查resource==null沒(méi)有加鎖,所以可能同時(shí)兩個(gè)線程進(jìn)入if并且請(qǐng)求加鎖,所以第一個(gè)拿到鎖的初始化一次,第二次拿鎖的會(huì)再次check。這看起來(lái)很完美:大多數(shù)情況下resouce不為空,很少的情況(剛開(kāi)始時(shí))resource為空,那么再加鎖,這比一上來(lái)就加鎖要高效很多不過(guò)千萬(wàn)別高興地太早了,因?yàn)榫幾g器對(duì)引用的賦值可能會(huì)做優(yōu)化,可能這個(gè)對(duì)象還沒(méi)有正確的構(gòu)造好,值已經(jīng)賦好了(為什么要這么做?也許構(gòu)造對(duì)象需要IO,io等待的時(shí)間把值賦好了能提高速度)。這個(gè)時(shí)候別的線程就慘了!另外很多講延遲加載的文章都比較早(早于jdk6),那個(gè)年代java的synchronized確實(shí)很不給力。如果你實(shí)在在乎這點(diǎn)性能的話,應(yīng)該用jvm的靜態(tài)類(lèi)加載機(jī)制來(lái)實(shí)現(xiàn):

@ThreadSafe
public class ResourceFactory {
   private static class ResourceHolder {
     public static Resource resource = new Resource();
   }
 
   public static Resource getResource() {
     return ResourceHolder.resource ;
   }
}

到此這篇關(guān)于Java多線程中Lock鎖的使用總結(jié)的文章就介紹到這了,更多相關(guān)Java多線程 Lock鎖的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring IOC源碼之bean的注冊(cè)過(guò)程講解

    Spring IOC源碼之bean的注冊(cè)過(guò)程講解

    這篇文章主要介紹了Spring IOC源碼之bean的注冊(cè)過(guò)程講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • json序列化時(shí)忽略值為null的字段2種方式實(shí)例

    json序列化時(shí)忽略值為null的字段2種方式實(shí)例

    這篇文章主要給大家介紹了關(guān)于json序列化時(shí)忽略值為null的字段的2種方式,當(dāng)對(duì)象中某個(gè)字段為null時(shí),我們希望將對(duì)象轉(zhuǎn)換成json時(shí)為null的字段不會(huì)被轉(zhuǎn)換到j(luò)son字符串,里面需要的朋友可以參考下
    2023-10-10
  • Java8接口的默認(rèn)方法

    Java8接口的默認(rèn)方法

    這篇文章主要為大家介紹了Java8接口的默認(rèn)方法,還為大家默認(rèn)方法的多重繼承,感興趣的朋友可以參考一下
    2016-01-01
  • JAVA實(shí)現(xiàn)301永久重定向方法

    JAVA實(shí)現(xiàn)301永久重定向方法

    本篇文章給大家總結(jié)了JAVA中實(shí)現(xiàn)永久重定向的方法以及詳細(xì)代碼,對(duì)此有需要的朋友可以參考學(xué)習(xí)下。
    2018-04-04
  • JAVA多線程間通訊常用實(shí)現(xiàn)方法解析

    JAVA多線程間通訊常用實(shí)現(xiàn)方法解析

    這篇文章主要介紹了JAVA多線程間通訊常用實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Java postgresql數(shù)組字段類(lèi)型處理方法詳解

    Java postgresql數(shù)組字段類(lèi)型處理方法詳解

    這篇文章主要介紹了Java postgresql數(shù)組字段類(lèi)型處理方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • Jax-rs規(guī)范下REST接口使用方法詳解

    Jax-rs規(guī)范下REST接口使用方法詳解

    這篇文章主要介紹了Jax-rs規(guī)范下REST接口使用方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • 解決nacos修改配置信息后需要重啟服務(wù)才能生效的問(wèn)題

    解決nacos修改配置信息后需要重啟服務(wù)才能生效的問(wèn)題

    當(dāng)配置信息發(fā)生變動(dòng)時(shí),傳統(tǒng)修改配置信息后,需要重新重啟服務(wù)器才可以生效,大量應(yīng)用配置修改時(shí),需要一個(gè)個(gè)修改配置,無(wú)法統(tǒng)一修改,且沒(méi)有辦法回溯配置版本,所以本文給大家介紹了如何解決這些問(wèn)題的方法,需要的朋友可以參考下
    2023-10-10
  • Java OpenSSL生成的RSA公私鑰進(jìn)行數(shù)據(jù)加解密詳細(xì)介紹

    Java OpenSSL生成的RSA公私鑰進(jìn)行數(shù)據(jù)加解密詳細(xì)介紹

    這篇文章主要介紹了Java OpenSSL生成的RSA公私鑰進(jìn)行數(shù)據(jù)加解密詳細(xì)介紹的相關(guān)資料,這里提供實(shí)例代碼及說(shuō)明具體如何實(shí)現(xiàn),需要的朋友可以參考下
    2016-12-12
  • Springboot實(shí)現(xiàn)全局自定義異常的方法詳解

    Springboot實(shí)現(xiàn)全局自定義異常的方法詳解

    這篇文章主要介紹了Springboot實(shí)現(xiàn)全局自定義異常的方法詳解,SpringBoot的項(xiàng)目已經(jīng)對(duì)有一定的異常處理了,但是對(duì)于我們開(kāi)發(fā)者而言可能就不太合適了,因此我們需要對(duì)這些異常進(jìn)行統(tǒng)一的捕獲并處理,需要的朋友可以參考下
    2023-11-11

最新評(píng)論