最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)編程之顯式鎖機(jī)制詳解

 更新時(shí)間:2017年10月26日 15:43:00   作者:Single_Yam  
這篇文章主要為大家詳細(xì)介紹了Java并發(fā)編程之顯式鎖機(jī)制的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

        我們之前介紹過(guò)synchronized關(guān)鍵字實(shí)現(xiàn)程序的原子性操作,它的內(nèi)部也是一種加鎖和解鎖機(jī)制,是一種聲明式的編程方式,我們只需要對(duì)方法或者代碼塊進(jìn)行聲明,Java內(nèi)部幫我們?cè)谡{(diào)用方法之前和結(jié)束時(shí)加鎖和解鎖。而我們本篇將要介紹的顯式鎖是一種手動(dòng)式的實(shí)現(xiàn)方式,程序員控制鎖的具體實(shí)現(xiàn),雖然現(xiàn)在越來(lái)越趨向于使用synchronized直接實(shí)現(xiàn)原子操作,但是了解了Lock接口的具體實(shí)現(xiàn)機(jī)制將有助于我們對(duì)synchronized的使用。本文主要涉及以下一些內(nèi)容:

  • 接口Lock的基本組成成員
  • 可重入鎖ReentrantLock的基本使用
  • 深入ReentrantLock的實(shí)現(xiàn)原理

一、接口Lock的基本組成成員

Lock 位于java.util.concurrent.locks包下,源碼如下:

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

其中,

void lock();:調(diào)用該方法將獲得一個(gè)鎖的入口
lockInterruptibly():該方法也是去獲得一個(gè)鎖,但是它是響應(yīng)中斷的,一旦在獲取的過(guò)程中遭遇中斷將拋出 InterruptedException。
boolean tryLock();:該方法嘗試著去獲得一個(gè)鎖,如果獲取失敗將返回false,并不會(huì)阻塞當(dāng)前線程
boolean tryLock(long time, TimeUnit unit):嘗試著去獲取一個(gè)鎖,如果獲取失敗,將阻塞等待指定的時(shí)間,期間如果能夠獲得鎖將返回true,否則返回false,響應(yīng)中斷請(qǐng)求。
void unlock();:釋放一個(gè)鎖
Condition newCondition();:條件變量,留待下篇文章學(xué)習(xí)

二、可重入鎖ReentrantLock的基本使用

     ReentrantLock是接口 Lock的一個(gè)最主要的實(shí)現(xiàn)類(lèi),不僅實(shí)現(xiàn)了Lock中的基本的加鎖釋放鎖的方法,還擴(kuò)展了自己的方法。它有兩個(gè)構(gòu)造方法:

public ReentrantLock() {
  sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

參數(shù) fair用于保證鎖機(jī)制的公平策略,公平的策略會(huì)是的等待時(shí)間越長(zhǎng)的線程優(yōu)先獲得鎖。保證公平必然會(huì)降低性能,所以ReentrantLock默認(rèn)并不保證公平。我們用ReentrantLock來(lái)實(shí)現(xiàn)對(duì)程序的原子操作:

public class MyThread extends Thread{
  
  private static Lock lock = new ReentrantLock();
  public static int count;
  
  @Override
  public void run() {
    try {
      Thread.sleep((int)Math.random()*100);
      lock.lock();
      count++;
      lock.unlock();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

當(dāng)我們?cè)谥鞒绦蛑袉?dòng)一百個(gè)線程隨機(jī)喚醒對(duì)count進(jìn)行加一時(shí),無(wú)論運(yùn)行多少次,結(jié)果都是一百,也就是說(shuō)我們的ReentrantLock是可以為我們保證原子操作的。

ReentrantLock還有一個(gè)特性就是可以重入性,即在本身獲得某個(gè)鎖的前提下可以隨意進(jìn)入被該鎖鎖住的其他方法,對(duì)于一個(gè)鎖可以重復(fù)進(jìn)入。除此之外,ReentrantLock還具有一些其他的有關(guān)鎖信息的方法:

  • public int getHoldCount():表示當(dāng)前線程持有該鎖的數(shù)量
  • public boolean isHeldByCurrentThread():判斷鎖是否為當(dāng)前線程持有
  • public boolean isLocked():判斷鎖是否為任意一個(gè)線程持有,如果有則返回true,否則返回false
  • public final boolean hasQueuedThreads():判斷該鎖上是否有線程進(jìn)行等待
  • public final int getQueueLength():返回當(dāng)前等待隊(duì)列的長(zhǎng)度,也就是等待進(jìn)入該鎖的線程個(gè)數(shù)

三、深入ReentrantLock的實(shí)現(xiàn)原理

ReentrantLock依賴(lài)CAS和LockSupport來(lái)實(shí)現(xiàn),LockSupport有點(diǎn)像工具類(lèi),它主要提供兩類(lèi)方法,park和unpark。

  • public static void park()
  • public static void parkNanos(long nanos)
  • public static void parkUntil(long deadline)
  • public static void unpark(Thread thread)

調(diào)用park方法會(huì)使得當(dāng)前線程丟失CPU使用權(quán),從Runnable狀態(tài)轉(zhuǎn)變?yōu)閃aiting狀態(tài)。而unpark方法則反過(guò)來(lái)讓W(xué)aiting狀態(tài)的某個(gè)線程轉(zhuǎn)變狀態(tài)為Runnable,等待操作系統(tǒng)調(diào)度。parkNanos和parkUntil是和時(shí)間相關(guān)的兩個(gè)park的變種,parkNanos指定線程要等待的時(shí)間,parkUntil則指定線程要等待到什么時(shí)候,這個(gè)時(shí)間是一個(gè)絕對(duì)時(shí)間,相對(duì)于紀(jì)元的毫秒數(shù)。

Java的并發(fā)包中有很多并發(fā)工具,ReentrantReadWriteLock,Semaphore,CountDownLatch,ReentrantLock等。這些工具有很多的共同特性,于是Java為我們抽象了一個(gè)類(lèi)AbstractQueuedSynchronizer(AQS)來(lái)表示這些工具的共性。ReentrantLock是其的一個(gè)實(shí)現(xiàn)類(lèi),內(nèi)部有三個(gè)內(nèi)部類(lèi):

abstract static class Sync extends AbstractQueuedSynchronizer{
  //......
}
static final class NonfairSync extends Sync{
  //...........
}
static final class FairSync extends Sync {
  //.............
}

Sync 繼承了AQS并對(duì)其中的大部分代碼進(jìn)行了簡(jiǎn)單的實(shí)現(xiàn),F(xiàn)airSync 和NonfairSync 是針對(duì)公平策略而定義的,如果構(gòu)造ReentrantLock的時(shí)候指定公平的策略,那么其內(nèi)部的所有方法都依賴(lài)這個(gè)FairSync ,否則就全部依賴(lài)NonfairSync。接著看ReentrantLock的構(gòu)造函數(shù):

private final Sync sync;

public ReentrantLock() {
  sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

兩個(gè)構(gòu)造方法最終會(huì)對(duì)sync進(jìn)行初始化,而sync的將在后續(xù)的方法中起到相當(dāng)大的作用。我們先看lock方法的具體實(shí)現(xiàn):

public void lock() {
  sync.lock();
}

ReentrantLock的lock方法調(diào)用的sync的lock方法,而在sync中的lock方法是一個(gè)抽象的方法,也就是說(shuō)這個(gè)方法的具體實(shí)現(xiàn)在子類(lèi)中,我們看NonfairSync中的實(shí)現(xiàn):

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

AQS中有一個(gè)整型類(lèi)型的State變量,它用于標(biāo)識(shí)當(dāng)前鎖被持有的次數(shù),該值為0表示當(dāng)前鎖沒(méi)有被任何線程持有。compareAndSetState是AQS中的方法,該方法調(diào)用了unsafe.compareAndSwapInt方法以CAS方式對(duì)State進(jìn)行了更新,如果state的值為0,說(shuō)明該鎖并沒(méi)有被任何線程持有,那么當(dāng)前線程將持有該鎖并將state的值賦為1。

這就完成了獲取的動(dòng)作,一旦后續(xù)的線程嘗試訪問(wèn)臨界區(qū)代碼,在前面的線程沒(méi)有釋放鎖之前,將會(huì)調(diào)用 acquire(1)。

public final void acquire(int arg) {
  if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
}

tryAcquire還是調(diào)用了AQS中的實(shí)現(xiàn),

final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();
  int c = getState();
  if (c == 0) {
    if (compareAndSetState(0, acquires)) {
      setExclusiveOwnerThread(current);
      return true;
    }
  }
  else if (current == getExclusiveOwnerThread()) {
    int nextc = c + acquires;
    if (nextc < 0) // overflow
      throw new Error("Maximum lock count exceeded");
    setState(nextc);
    return true;
  }
  return false;
}

第一個(gè)if判斷,想要持有的鎖是否被持有(雖然之前判斷過(guò)了,但是有可能在我們調(diào)用nonfairTryAcquire方法的期間,之前的線程釋放了該鎖),如果未被任何線程持有,那么將直接持有該鎖。

第二個(gè)if判斷,如果當(dāng)前鎖的持有者就是當(dāng)前線程,表示這是同線程的重入操作,于是增加鎖定次數(shù)并設(shè)置state的值。

整個(gè)方法結(jié)束之后,如果當(dāng)前線程獲得了鎖,都將返回true,否則都會(huì)返回false。而如果tryAcquire方法返回true,那么整個(gè)acquire方法也將結(jié)束,否則就說(shuō)明當(dāng)前線程并沒(méi)有通過(guò)鎖,需要被阻塞。那么就會(huì)調(diào)用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法。

private Node addWaiter(Node mode) {
  Node node = new Node(Thread.currentThread(), mode);
  Node pred = tail;
  if (pred != null) {
    node.prev = pred;
    if (compareAndSetTail(pred, node)) {
      pred.next = node;
      return node;
    }
  }
  enq(node);
  return node;
}

addWaiter方法將當(dāng)前線程包裹成一個(gè)Node結(jié)點(diǎn),添加到AQS內(nèi)部所維護(hù)的一個(gè)等待隊(duì)列并返回該Node結(jié)點(diǎn)。最后調(diào)用acquireQueued方法:

final boolean acquireQueued(final Node node, int arg) {
  boolean failed = true;
  try {
    boolean interrupted = false;
    for (;;) {
      final Node p = node.predecessor();
      if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return interrupted;
      }
      if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        interrupted = true;
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

該方法首先會(huì)去獲得node的前一個(gè)結(jié)點(diǎn),判斷如果是head結(jié)點(diǎn),那么說(shuō)明當(dāng)前的node結(jié)點(diǎn)是整個(gè)等待隊(duì)列上的第一個(gè)等待的結(jié)點(diǎn)。于是讓它嘗試著去獲得鎖,如果能夠獲得鎖,將從等待隊(duì)列中清除它并返回。

如果發(fā)現(xiàn)當(dāng)前結(jié)點(diǎn)前面還有等待的結(jié)點(diǎn)或者嘗試獲取鎖失敗,那么將會(huì)調(diào)用shouldParkAfterFailedAcquire方法判斷該結(jié)點(diǎn)鎖對(duì)應(yīng)的線程是否需要被unpark阻塞,并最終調(diào)用LockSupport.park(this)阻塞當(dāng)前線程。

在第一個(gè)線程持有該鎖的前提下,成功阻塞了第二個(gè)線程。這大概就是整個(gè)lock方法的調(diào)用鏈流程。

接下來(lái)看看unlock的具體實(shí)現(xiàn),

public void unlock() {
  sync.release(1);
}

這是ReentrantLock中對(duì)AQS的unlock的具體實(shí)現(xiàn),調(diào)用了sync的release方法,這個(gè)方法是其父類(lèi)AQS中的方法:

public final boolean release(int arg) {
  if (tryRelease(arg)) {
    Node h = head;
    if (h != null && h.waitStatus != 0)
      unparkSuccessor(h);
    return true;
  }
  return false;
}

tryRelease被sync重寫(xiě),具體代碼如下:

protected final boolean tryRelease(int releases) {
  int c = getState() - releases;
  if (Thread.currentThread() != getExclusiveOwnerThread())
    throw new IllegalMonitorStateException();
   boolean free = false;
  if (c == 0) {
    free = true;
    setExclusiveOwnerThread(null);
  }
  setState(c);
  return free;
}

首先判斷如果當(dāng)前線程并不是鎖的當(dāng)前持有者,拋出異常(不持有該鎖自然不能釋放該鎖)。如果c等于0則表示,當(dāng)前鎖只被持有一次,也就是當(dāng)前線程并沒(méi)有多次重入該鎖,于是將該鎖的持有者設(shè)置為null,表示未被任何線程持有。如果c不等于0,那么說(shuō)明該鎖被當(dāng)前線程重入多次,于是對(duì)state減一并設(shè)置state的值。最終如果返回true則說(shuō)明該鎖被釋放了,否則說(shuō)明當(dāng)前線程依然持有該鎖。

回到release方法,如果tryRelease(arg)返回true,那么方法體會(huì)判斷當(dāng)前等待隊(duì)列是否有結(jié)點(diǎn)在等待該鎖,如果有則調(diào)用unparkSuccessor(h)方法喚醒等待隊(duì)列上的第一個(gè)等待的結(jié)點(diǎn)線程并返回true。

這里有一個(gè)細(xì)節(jié),其實(shí)所有未能獲得鎖的線程都被阻塞在方法中:

final boolean acquireQueued(final Node node, int arg) {
  boolean failed = true;
  try {
    boolean interrupted = false;
    for (;;) {
      final Node p = node.predecessor();
      if (p == head && tryAcquire(arg)) {
        setHead(node);
        p.next = null; // help GC
        failed = false;
        return interrupted;
      }
      if (shouldParkAfterFailedAcquire(p, node) &&
        parkAndCheckInterrupt())
        //******等待線程喚醒的起始位置********//
        interrupted = true;
    }
  } finally {
    if (failed)
      cancelAcquire(node);
  }
}

未能獲得鎖的線程被方法parkAndCheckInterrupt阻塞了,所以當(dāng)我們?cè)趗nlock中調(diào)用unpark喚醒一個(gè)等待隊(duì)列上的線程結(jié)點(diǎn)時(shí),線程將從此處重新進(jìn)入死循環(huán)嘗試去獲取鎖。如果能夠獲得鎖,將從等待隊(duì)列中移除自己,并返回,否則再次被阻塞等待喚醒。

整個(gè)unlock方法的執(zhí)行流程也已經(jīng)大致介紹完成,最后我們看看可重入鎖ReentrantLock和synchronized的一些對(duì)比。

四、ReentrantLock對(duì)比synchronized

     synchronized更傾向于一種聲明式的編程方式,我們?cè)诜椒ㄇ笆褂胹ynchronized修飾,Java會(huì)自動(dòng)為我們實(shí)現(xiàn)其內(nèi)部的細(xì)節(jié),什么時(shí)候加鎖,什么時(shí)候釋放鎖都是它負(fù)責(zé)的。
     而對(duì)于我們的ReentrantLock重入鎖來(lái)說(shuō),需要我們自己手動(dòng)的去加鎖和釋放鎖,對(duì)于邏輯的要求更高,也相對(duì)更難。
     而隨著jvm版本的更新和優(yōu)化,ReentrantLock和synchronized在性能上的差別在逐漸縮小,所以一般建議使用synchronized而盡量避免復(fù)雜難操作的ReentrantLock。

對(duì)于顯式鎖的基本情況大致介紹如上,如有錯(cuò)誤之處,望指出!

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java代碼中4種字符串拼接方式分析

    Java代碼中4種字符串拼接方式分析

    本文主要介紹了Java代碼中4種字符串拼接方式分析,主要介紹了“+”號(hào)、StringBuilder、StringJoiner、String#join,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • SpringBoot用實(shí)體接收Get請(qǐng)求傳遞過(guò)來(lái)的多個(gè)參數(shù)的兩種方式

    SpringBoot用實(shí)體接收Get請(qǐng)求傳遞過(guò)來(lái)的多個(gè)參數(shù)的兩種方式

    本文主要介紹SpringBoot用實(shí)體接收Get請(qǐng)求傳遞過(guò)來(lái)的多個(gè)參數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • 帶你入門(mén)Java的數(shù)組

    帶你入門(mén)Java的數(shù)組

    這篇文章主要給大家介紹了關(guān)于Java中數(shù)組的定義和使用的相關(guān)資料,,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • 詳解Hibernate緩存與性能優(yōu)化

    詳解Hibernate緩存與性能優(yōu)化

    在hibernate中,提到性能優(yōu)化,很自然地我們就想到了緩存。緩存是什么,都有哪些呢?下面這篇文章就主要給大家介紹了關(guān)于Hibernate緩存與性能優(yōu)化的相關(guān)資料,需要的朋友可以參考下。
    2017-02-02
  • Spring Boot讀取自定義配置文件

    Spring Boot讀取自定義配置文件

    在Spring Boot項(xiàng)目中我們經(jīng)常需要讀取application.yml配置文件的自定義配置,今天就來(lái)羅列一下從yaml讀取配置文件的一些常用手段和方法。
    2021-05-05
  • 詳解springMVC容器加載源碼分析

    詳解springMVC容器加載源碼分析

    這篇文章主要介紹了詳解springMVC容器加載源碼分析,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-05-05
  • SpringBoot整合Hibernate Validator實(shí)現(xiàn)參數(shù)驗(yàn)證功能

    SpringBoot整合Hibernate Validator實(shí)現(xiàn)參數(shù)驗(yàn)證功能

    這篇文章主要介紹了SpringBoot整合Hibernate Validator實(shí)現(xiàn)參數(shù)驗(yàn)證功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • Java的wait(), notify()和notifyAll()使用心得

    Java的wait(), notify()和notifyAll()使用心得

    本篇文章是對(duì)java的 wait(),notify(),notifyAll()進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-08-08
  • Springboot?+redis+谷歌開(kāi)源Kaptcha實(shí)現(xiàn)圖片驗(yàn)證碼功能

    Springboot?+redis+谷歌開(kāi)源Kaptcha實(shí)現(xiàn)圖片驗(yàn)證碼功能

    這篇文章主要介紹了Springboot?+redis+?歌開(kāi)源Kaptcha實(shí)現(xiàn)圖片驗(yàn)證碼功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-01-01
  • 詳解Java的設(shè)計(jì)模式編程中的原型模式

    詳解Java的設(shè)計(jì)模式編程中的原型模式

    這篇文章主要介紹了Java的設(shè)計(jì)模式編程中的原型模式,處理對(duì)象復(fù)制時(shí)要特別注意淺拷貝和深拷貝的問(wèn)題,需要的朋友可以參考下
    2016-02-02

最新評(píng)論

宁河县| 微山县| 晋宁县| 和田县| 光山县| 库车县| 安图县| 镇赉县| 霸州市| 溧阳市| 盱眙县| 微山县| 盘锦市| 西宁市| 成都市| 龙海市| 宝坻区| 普安县| 新泰市| 香港| 贵港市| 大冶市| 开鲁县| 石阡县| 涟水县| 潜江市| 敦化市| 甘肃省| 城市| 新乡县| 大理市| 孟村| 荔波县| 两当县| 黑河市| 玉山县| 水富县| 博客| 抚顺市| 河津市| 邯郸市|