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

Java中的synchronized和ReentrantLock的區(qū)別詳細(xì)解讀

 更新時(shí)間:2024年01月03日 09:48:23   作者:搬山道猿  
這篇文章主要介紹了Java中的synchronized和ReentrantLock的區(qū)別詳細(xì)解讀,synchronized是Java內(nèi)建的同步機(jī)制,所以也有人稱(chēng)其為 IntrinsicLocking,它提供了互斥的語(yǔ)義和可見(jiàn)性,當(dāng)一個(gè)線(xiàn)程已經(jīng)獲取當(dāng)前鎖時(shí),其他試圖獲取的線(xiàn)程只能等待或者阻塞在那里,需要的朋友可以參考下

前言

軟件并發(fā)已經(jīng)成為現(xiàn)代軟件開(kāi)發(fā)的基礎(chǔ)能力,而 Java 精心設(shè)計(jì)的高效并發(fā)機(jī)制,正是構(gòu)建大規(guī)模應(yīng)用的基礎(chǔ)之一。

本篇博文的重點(diǎn)是,synchronized 和 ReentrantLock 有什么區(qū)別? 有人說(shuō) synchronized 最慢,這話(huà)靠譜嗎?  

常見(jiàn)回答

synchronized 是 Java 內(nèi)建的同步機(jī)制,所以也有人稱(chēng)其為 Intrinsic Locking,它提供了互斥的語(yǔ)義和可見(jiàn)性,當(dāng)一個(gè)線(xiàn)程已經(jīng)獲取當(dāng)前鎖時(shí),其他試圖獲取的線(xiàn)程只能等待或者阻塞在那里。

在 Java 5 以前,synchronized 是僅有的同步手段,在代碼中, synchronized 可以用來(lái)修飾方法,也可以使用在特定的代碼塊兒上,本質(zhì)上 synchronized 方法等同于把方法全部語(yǔ)句用 synchronized 塊包起來(lái)。

ReentrantLock,通常翻譯為再入鎖,是 Java 5 提供的鎖實(shí)現(xiàn),它的語(yǔ)義和 synchronized 基本相同。再入鎖通過(guò)代碼直接調(diào)用 lock() 方法獲取,代碼書(shū)寫(xiě)也更加靈活。與此同時(shí),ReentrantLock 提供了很多實(shí)用的方法,能夠?qū)崿F(xiàn)很多 synchronized 無(wú)法做到的細(xì)節(jié)控制,比如可以控制 fairness,也就是公平性,或者利用定義條件等。但是,編碼中也需要注意,必須要明確調(diào)用 unlock() 方法釋放,不然就會(huì)一直持有該鎖。

synchronized 和 ReentrantLock 的性能不能一概而論,早期版本 synchronized 在很多場(chǎng)景下性能相差較大,在后續(xù)版本進(jìn)行了較多改進(jìn),在低競(jìng)爭(zhēng)場(chǎng)景中表現(xiàn)可能優(yōu)于 ReentrantLock。  

具體分析

對(duì)于并發(fā)編程,不同公司或者面試官面試風(fēng)格也不一樣,有個(gè)別大廠喜歡一直追問(wèn)你相關(guān)機(jī)制的擴(kuò)展或者底層,有的喜歡從實(shí)用角度出發(fā),所以你在準(zhǔn)備并發(fā)編程方面需要一定的耐心。

鎖作為并發(fā)的基礎(chǔ)工具之一,至少需要掌握:

  • 理解什么是線(xiàn)程安全。
  • synchronized、ReentrantLock 等機(jī)制的基本使用與案例。

更進(jìn)一步,你還需要:

  • 掌握 synchronized、ReentrantLock 底層實(shí)現(xiàn);理解鎖膨脹、降級(jí);理解偏斜鎖、自旋鎖、輕量級(jí)鎖、重量級(jí)鎖等概念。
  • 掌握并發(fā)包中 java.util.concurrent.lock 各種不同實(shí)現(xiàn)和案例分析。  

實(shí)戰(zhàn)剖析

首先,我們需要理解什么是線(xiàn)程安全。

在 Brain Goetz 等專(zhuān)家撰寫(xiě)的《Java 并發(fā)編程實(shí)戰(zhàn)》(Java Concurrency in Practice)中,線(xiàn)程安全是一個(gè)多線(xiàn)程環(huán)境下正確性的概念,也就是保證多線(xiàn)程環(huán)境下共享的、可修改的狀態(tài)的正確性,這里的狀態(tài)反映在程序中其實(shí)可以看作是數(shù)據(jù)。

換個(gè)角度來(lái)看,如果狀態(tài)不是共享的,或者不是可修改的,也就不存在線(xiàn)程安全問(wèn)題,進(jìn)而可以推理出保證線(xiàn)程安全的兩個(gè)辦法:

  • 封裝:通過(guò)封裝,我們可以將對(duì)象內(nèi)部狀態(tài)隱藏、保護(hù)起來(lái)。
  • 不可變:final 和 immutable 就是這個(gè)道理,Java 語(yǔ)言目前還沒(méi)有真正意義上的原生不可變,但是未來(lái)也許會(huì)引入。

線(xiàn)程安全需要保證幾個(gè)基本特性:

  • 原子性,簡(jiǎn)單說(shuō)就是相關(guān)操作不會(huì)中途被其他線(xiàn)程干擾,一般通過(guò)同步機(jī)制實(shí)現(xiàn)。
  • 可見(jiàn)性,是一個(gè)線(xiàn)程修改了某個(gè)共享變量,其狀態(tài)能夠立即被其他線(xiàn)程知曉,通常被解釋為將線(xiàn)程本地狀態(tài)反映到主內(nèi)存上,volatile 就是負(fù)責(zé)保證可見(jiàn)性的。
  • 有序性,是保證線(xiàn)程內(nèi)串行語(yǔ)義,避免指令重排等。

可能有點(diǎn)晦澀,那么我們看看下面的代碼段,分析一下原子性需求體現(xiàn)在哪里。這個(gè)例子通過(guò)取兩次數(shù)值然后進(jìn)行對(duì)比,來(lái)模擬兩次對(duì)共享狀態(tài)的操作。

你可以編譯并執(zhí)行,可以看到,僅僅是兩個(gè)線(xiàn)程的低度并發(fā),就非常容易碰到 former 和 latter 不相等的情況。這是因?yàn)?,在兩次取值的過(guò)程中,其他線(xiàn)程可能已經(jīng)修改了 sharedState。

public class ThreadSafeSample {
  public int sharedState;
  public void nonSafeAction() {
      while (sharedState < 100000) {
          int former = sharedState++;
          int latter = sharedState;
          if (former != latter - 1) {
              System.out.printf("Observed data race, former is " +
                      former + ", " + "latter is " + latter);
          }
      }
  }
 
  public static void main(String[] args) throws InterruptedException {
      ThreadSafeSample sample = new ThreadSafeSample();
      Thread threadA = new Thread(){
          public void run(){
              sample.nonSafeAction();
          }
      };
      Thread threadB = new Thread(){
          public void run(){
              sample.nonSafeAction();
          }
      };
      threadA.start();
      threadB.start();
      threadA.join();
      threadB.join();
  }
}

以下是某次運(yùn)行結(jié)果:

Observed data race, former is 9851, latter is 9853

將兩次賦值過(guò)程用 synchronized 保護(hù)起來(lái),使用 this 作為互斥單元,就可以避免別的線(xiàn)程并發(fā)的去修改 sharedState。

synchronized (this) {
  int former = sharedState ++;
  int latter = sharedState;
  // …
}

如果用 javap 反編譯,可以看到類(lèi)似片段,利用 monitorenter/monitorexit 對(duì)實(shí)現(xiàn)了同步的語(yǔ)義:

11: astore_1
12: monitorenter
13: aload_0
14: dup
15: getfield    #2                // Field sharedState:I
18: dup_x1
…
56: monitorexit

代碼中使用 synchronized 非常便利,如果用來(lái)修飾靜態(tài)方法,其等同于利用下面代碼將方法體囊括進(jìn)來(lái):

synchronized (ClassName.class) {}

再來(lái)看看 ReentrantLock。你可能好奇什么是再入?它是表示當(dāng)一個(gè)線(xiàn)程試圖獲取一個(gè)它已經(jīng)獲取的鎖時(shí),這個(gè)獲取動(dòng)作就自動(dòng)成功,這是對(duì)鎖獲取粒度的一個(gè)概念,也就是鎖的持有是以線(xiàn)程為單位而不是基于調(diào)用次數(shù)。Java 鎖實(shí)現(xiàn)強(qiáng)調(diào)再入性是為了和 pthread 的行為進(jìn)行區(qū)分。

再入鎖可以設(shè)置公平性(fairness),我們可在創(chuàng)建再入鎖時(shí)選擇是否是公平的。

ReentrantLock fairLock = new ReentrantLock(true);

這里所謂的公平性是指在競(jìng)爭(zhēng)場(chǎng)景中,當(dāng)公平性為真時(shí),會(huì)傾向于將鎖賦予等待時(shí)間最久的線(xiàn)程。公平性是減少線(xiàn)程“饑餓”(個(gè)別線(xiàn)程長(zhǎng)期等待鎖,但始終無(wú)法獲?。┣闆r發(fā)生的一個(gè)辦法。

如果使用 synchronized,我們根本無(wú)法進(jìn)行公平性的選擇,其永遠(yuǎn)是不公平的,這也是主流操作系統(tǒng)線(xiàn)程調(diào)度的選擇。通用場(chǎng)景中,公平性未必有想象中的那么重要,Java 默認(rèn)的調(diào)度策略很少會(huì)導(dǎo)致 “饑餓”發(fā)生。與此同時(shí),若要保證公平性則會(huì)引入額外開(kāi)銷(xiāo),自然會(huì)導(dǎo)致一定的吞吐量下降。所以,我建議只有當(dāng)你的程序確實(shí)有公平性需要的時(shí)候,才有必要指定它。

我們?cè)購(gòu)娜粘>幋a的角度學(xué)習(xí)下再入鎖。為保證鎖釋放,每一個(gè) lock() 動(dòng)作,我建議都立即對(duì)應(yīng)一個(gè) try-catch-finally,典型的代碼結(jié)構(gòu)如下,這是個(gè)良好的習(xí)慣。

ReentrantLock fairLock = new ReentrantLock(true);// 這里是演示創(chuàng)建公平鎖,一般情況不需要。
fairLock.lock();
try {
  // do something
} finally {
   fairLock.unlock();
}

ReentrantLock 相比 synchronized,因?yàn)榭梢韵衿胀▽?duì)象一樣使用,所以可以利用其提供的各種便利方法,進(jìn)行精細(xì)的同步操作,甚至是實(shí)現(xiàn) synchronized 難以表達(dá)的用例,如:

  • 帶超時(shí)的獲取鎖嘗試。
  • 可以判斷是否有線(xiàn)程,或者某個(gè)特定線(xiàn)程,在排隊(duì)等待獲取鎖。
  • 可以響應(yīng)中斷請(qǐng)求。
  • ...

這里我特別想強(qiáng)調(diào)條件變量(java.util.concurrent.Condition),如果說(shuō) ReentrantLock 是 synchronized 的替代選擇,Condition 則是將 wait、notify、notifyAll 等操作轉(zhuǎn)化為相應(yīng)的對(duì)象,將復(fù)雜而晦澀的同步操作轉(zhuǎn)變?yōu)橹庇^可控的對(duì)象行為。

條件變量最為典型的應(yīng)用場(chǎng)景就是標(biāo)準(zhǔn)類(lèi)庫(kù)中的 ArrayBlockingQueue 等。

參考下面的源碼,首先,通過(guò)再入鎖獲取條件變量:

 
/** Condition for waiting takes */
private final Condition notEmpty;
 
/** Condition for waiting puts */
private final Condition notFull;
 
public ArrayBlockingQueue(int capacity, boolean fair) {
  if (capacity <= 0)
      throw new IllegalArgumentException();
  this.items = new Object[capacity];
  lock = new ReentrantLock(fair);
  notEmpty = lock.newCondition();
  notFull =  lock.newCondition();
}

兩個(gè)條件變量是從同一再入鎖創(chuàng)建出來(lái),然后使用在特定操作中,如下面的 take 方法,判斷和等待條件滿(mǎn)足:

public E take() throws InterruptedException {
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();
  try {
      while (count == 0)
          notEmpty.await();
      return dequeue();
  } finally {
      lock.unlock();
  }
}

當(dāng)隊(duì)列為空時(shí),試圖 take 的線(xiàn)程的正確行為應(yīng)該是等待入隊(duì)發(fā)生,而不是直接返回,這是 BlockingQueue 的語(yǔ)義,使用條件 notEmpty 就可以?xún)?yōu)雅地實(shí)現(xiàn)這一邏輯。

那么,怎么保證入隊(duì)觸發(fā)后續(xù) take 操作呢?請(qǐng)看 enqueue 實(shí)現(xiàn):

private void enqueue(E e) {
  // assert lock.isHeldByCurrentThread();
  // assert lock.getHoldCount() == 1;
  // assert items[putIndex] == null;
  final Object[] items = this.items;
  items[putIndex] = e;
  if (++putIndex == items.length) putIndex = 0;
  count++;
  notEmpty.signal(); // 通知等待的線(xiàn)程,非空條件已經(jīng)滿(mǎn)足
}

通過(guò) signal/await 的組合,完成了條件判斷和通知等待線(xiàn)程,非常順暢就完成了狀態(tài)流轉(zhuǎn)。

注意,signal 和 await 成對(duì)調(diào)用非常重要,不然假設(shè)只有 await 動(dòng)作,線(xiàn)程會(huì)一直等待直到被打斷(interrupt)。

從性能角度,synchronized 早期的實(shí)現(xiàn)比較低效,對(duì)比 ReentrantLock,大多數(shù)場(chǎng)景性能都相差較大。

但是在 Java 6 中對(duì)其進(jìn)行了非常多的改進(jìn),可以參考性能對(duì)比,在高競(jìng)爭(zhēng)情況下,ReentrantLock 仍然有一定優(yōu)勢(shì)。

我在下一講進(jìn)行詳細(xì)分析,會(huì)更有助于理解性能差異產(chǎn)生的內(nèi)在原因。

在大多數(shù)情況下,無(wú)需糾結(jié)于性能,還是考慮代碼書(shū)寫(xiě)結(jié)構(gòu)的便利性、可維護(hù)性等。  

后記

以上就是 Java:synchronized 和 ReentrantLock 有什么區(qū)別呢? 的所有內(nèi)容了;

介紹了什么是線(xiàn)程安全,對(duì)比和分析了 synchronized 和 ReentrantLock,并針對(duì)條件變量等方面結(jié)合案例代碼進(jìn)行了介紹。

到此這篇關(guān)于Java中的synchronized和ReentrantLock的區(qū)別詳細(xì)解讀的文章就介紹到這了,更多相關(guān)synchronized和ReentrantLock區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java實(shí)現(xiàn)賬號(hào)登錄時(shí)發(fā)送郵件通知

    java實(shí)現(xiàn)賬號(hào)登錄時(shí)發(fā)送郵件通知

    這篇文章主要為大家詳細(xì)介紹了java如何實(shí)現(xiàn)在賬號(hào)登錄時(shí)發(fā)送郵件通知的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-09-09
  • web項(xiàng)目WEB-INF下沒(méi)有web.xml的解決方法

    web項(xiàng)目WEB-INF下沒(méi)有web.xml的解決方法

    新手如果在web項(xiàng)目創(chuàng)建后WEB-INF下面沒(méi)有出現(xiàn)web.xml,怎么辦?別慌,沒(méi)有web.xml文件的原因是因?yàn)樵趧?chuàng)建web項(xiàng)目的時(shí)候沒(méi)有把創(chuàng)建web.xml勾上。這篇文章主要介紹了web項(xiàng)目WEB-INF下沒(méi)有web.xml的解決方法,需要的朋友可以參考下
    2022-12-12
  • kotlin改善java代碼實(shí)例分析

    kotlin改善java代碼實(shí)例分析

    我們給大家整理了關(guān)于kotlin改善java代碼的相關(guān)實(shí)例以及操作的詳細(xì)方法,有需要的讀者們參考下。
    2018-03-03
  • springboot整合netty框架實(shí)現(xiàn)站內(nèi)信

    springboot整合netty框架實(shí)現(xiàn)站內(nèi)信

    Netty 是一個(gè)基于NIO的客戶(hù)、服務(wù)器端編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開(kāi)發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,這篇文章主要介紹了springboot整合netty框架的方式小結(jié),需要的朋友可以參考下
    2022-12-12
  • 詳解Java如何實(shí)現(xiàn)防止惡意注冊(cè)

    詳解Java如何實(shí)現(xiàn)防止惡意注冊(cè)

    惡意注冊(cè)通常是指使用自動(dòng)化腳本或者機(jī)器人在短時(shí)間內(nèi)進(jìn)行大量的注冊(cè)行為,這種行為會(huì)對(duì)系統(tǒng)造成壓力,甚至?xí)?dǎo)致系統(tǒng)癱瘓。所以本文為大家總結(jié)了一些防止惡意注冊(cè)的方法,需要的可以參考一下
    2023-04-04
  • Java?精煉解讀類(lèi)和對(duì)象原理

    Java?精煉解讀類(lèi)和對(duì)象原理

    面向?qū)ο竽耸荍ava語(yǔ)言的核心,是程序設(shè)計(jì)的思想。Java語(yǔ)言的面向?qū)ο蠹夹g(shù)包括了面向?qū)ο蠛兔嫦蜻^(guò)程的基本概念,面向?qū)ο蟮奶卣?,Java語(yǔ)言的類(lèi),對(duì)象,修飾符,抽象類(lèi)等一系列的知識(shí)點(diǎn)
    2022-03-03
  • java實(shí)現(xiàn)簡(jiǎn)單貪吃蛇小游戲

    java實(shí)現(xiàn)簡(jiǎn)單貪吃蛇小游戲

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單貪吃蛇小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • Java多線(xiàn)程中的Executor詳解

    Java多線(xiàn)程中的Executor詳解

    這篇文章主要介紹了Java多線(xiàn)程中的Executor詳解,該接口提供了一種將任務(wù)提交與如何運(yùn)行每個(gè)任務(wù)的機(jī)制(包括線(xiàn)程使用、調(diào)度等細(xì)節(jié))解耦的方法,它通常使用預(yù)先創(chuàng)建線(xiàn)程而不是創(chuàng)建線(xiàn)程,需要的朋友可以參考下
    2023-12-12
  • springboot異步處理@NotBlank或@NotNull注釋校驗(yàn)不生效問(wèn)題

    springboot異步處理@NotBlank或@NotNull注釋校驗(yàn)不生效問(wèn)題

    這篇文章主要介紹了springboot異步處理@NotBlank或@NotNull注釋校驗(yàn)不生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Java Math.round函數(shù)詳解

    Java Math.round函數(shù)詳解

    這篇文章主要介紹了Java Math.round函數(shù)詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08

最新評(píng)論