java多線程---ReentrantLock源碼分析

  ReentrantLock源碼分析node

  基礎知識複習數據結構

  synchronized和lock的區別併發

  synchronized是非公平鎖,沒法保證線程按照申請鎖的順序得到鎖,而Lock鎖提供了可選參數,能夠配置成公平鎖,也能夠配置成非公平鎖。一般來講,非公平鎖的效率比公平鎖要高。jvm

  一個線程使用syn獲取鎖,除非該線程成功獲取到鎖,不然將一直阻塞住。而Lock鎖提供了lockInterruptibly()接口,提供了可中斷的操做ide

  帶超時時間的鎖。Lock鎖提供了tryLock(long time, TimeUnit unit)帶超時時間的獲取鎖的接口,在等待指定時間後,若是獲取不到鎖,則放棄獲取鎖函數

  自動釋放鎖。若是用syn加鎖,當發生異常時(比方運行時異常),那麼jvm會自動釋放掉線程持有的鎖,而lock鎖則不會主動釋放,除非調用了unlock接口,所以使用lock鎖時有可能致使死鎖源碼分析

  在ReentrantLock上能夠綁定多個Condition條件,也就是能夠擁有多個等待隊列,好比在實現生產者消費者的時候,使用一個隊列(鎖的隊列)存放等待 隊列(生產者消費者的隊列)有元素的消費者,使用另外一個隊列(鎖的隊列)存放等待 隊列not full的生產者,相比較synchronized和wait notify而言,避免了錯誤的喚醒生產者或者消費者的開銷ui

  ReentrantLock 基礎分析spa

  ReentrantLock是lock的一個實現類,獨佔鎖。首先看一下ReentrantLock的內部屬性線程

  private final Sync sync;

  1

  發如今只有一個Sync類型的屬性,這個Sync是AQS的一個抽象類以下:

  abstract static class Sync extends AbstractQueuedSynchronizer {

  private static final long serialVersionUID = -5179523762034025860L;

  abstract void lock();

  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;

  }

  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;

  }

  ....

  同時在ReentrantLock 內部,實現了Sync2個不一樣的類,一個是NonfairSync(非公平鎖),一個是FairSync(公平鎖)。也就是在一開頭,synchronized和lock的區別的第一點,Lock能夠建立2種不一樣的鎖,根據傳入的參數。

  以下源碼

  //默認的ReentrantLock無參構造函數,是非公平鎖

  public ReentrantLock() {

  sync = new NonfairSync();

  }

  //當傳入fasle時,就建立了公平鎖。

  public ReentrantLock(boolean fair) {

  sync = fair ? new FairSync() : new NonfairSync();

  }

  AbstractQueuedSynchronizer

  那麼Sync的父類是AQS,併發包中的鎖底層就是使用了AQS:AbstractQueuedSynchronizer。因此來看一下AQS,那麼首先來看下屬性:

  //這個是父類AbstractOwnableSynchronizer中的屬性,標識了獨佔模式下獲取鎖的是哪個線程

  private transient Thread exclusiveOwnerThread;

  //AQS的數據結構是FIFO的雙向隊列

  //頭部節點

  private transient volatile Node head;

  //尾部節點

  private transient volatile Node tail;

  //同步狀態值

  private volatile int state;

  //設置這裏使用CAS來更新 主要用於對state的更新

  private static final Unsafe unsafe = Unsafe.getUnsafe();

  private static final long stateOffset;

  private static final long headOffset;

  private static final long tailOffset;

  private static final long waitStatusOffset;

  private static final long nextOffset;

  能夠看到有頭部節點和尾部節點外,還有一個同步狀態值state,該屬性在不一樣的鎖中,表明了不一樣的含義。

  在ReentrantLock中,state能夠用來表示當前線程獲取鎖的可重入次數

  對與ReentrantReadWriteLock來講,高16位表明了讀鎖,也就是讀鎖的次數,低16位表明了寫鎖的可重入次數

  在Semaphore中,state表明了可用信號的個數

  在countdownlatch中,state用來表示計數器當前的值

  因此在ReentrantLock 中 state用來表示當前線程獲取鎖的可衝入次數。當沒有線程持有鎖的時候,state爲0,當有一個線程獲取鎖時,state爲1。而後當前線程須要再次獲取鎖時,發現本身已是鎖的持有者,state+1

  看下AQS的Node:

  //是在獲取共享資源時放入隊列

  static final Node SHARED = new Node();

  //是在獲取獨佔資源時放入對列的

  static final Node EXCLUSIVE = null;

  //線程被取消

  static final int CANCELLED = 1;

  //線程須要被喚醒

  static final int SIGNAL = -1;

  //線程在條件隊列裏面等待

  static final int CONDITION = -2;

  //釋放共享資源時通知其餘節點

  static final int PROPAGATE = -3;

  //表示等待的狀態,能夠爲以上幾個值

  volatile int waitStatus;

  volatile Node prev;

  volatile Node next;

  //在隊列中的線程

  volatile Thread thread;

  Node nextWaiter;

  在已知隊列中的節點是這樣的結構狀況下,來看下何如進行獲取資源

  //根據源碼中的英文來翻譯,就是獲取獨佔資源時調用該方法,

  //也就是使用tryAcquire來改變state的值,若是失敗就會把線程封裝成Node.EXCLUSIVE放入隊列尾部並掛起

  public final void acquire(int arg) {

  if (!tryAcquire(arg) &&

  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

  selfInterrupt();

  }

  //其中 tryAcquire是在實現類中具體實現的

  對應的釋放資源

  //tryRelease是在實現類中具體實現的方法,若是成功釋放資源,則激活隊列中的一個線程,也就是頭部節點的線程。

  public final boolean release(int arg) {

  if (tryRelease(arg)) {

  Node h = head;

  if (h != null && h.waitStatus != 0)

  unparkSuccessor(h);

  return true;

  }

  return false;

  }

  以上2個release和acquire,的最多見的實現例子就是Lock中的unlock和lock。可是在AQS源碼中還存在着一個相似的方法,acquireInterruptibly ,這個方法和acquire功能同樣,可是帶Interruptibly 的方法,就是能夠對中斷進行響應。若是該線程在等待過程當中被中斷了,那麼帶Interruptibly 的方法就會拋出異常,也會終端。不然不會。對應了一開始的第二條,lockInterruptibly()能夠對中斷進行響應。

  再來看一下在AQS中,是怎麼進行加入隊列操做的。

  //有2種方式,若是尾部節點不爲空,就直接加入封裝好的node,爲空則調用enq()加入隊列

  private Node addWaiter(Node mode) {

  Node node = new Node(Thread.currentThread(), mode);

  // Try the fast path of enq; backup to full enq on failure

  Node pred = tail;

  if (pred != null) {

  node.prev = pred;

  if (compareAndSetTail(pred, node)) {

  pred.next = node;

  return node;

  }

  }

  enq(node);

  return node;

  }

  private Node enq(final Node node) {

  for (;;) {

  Node t = tail;

  if (t == null) { // Must initialize

  //加入一個空的node爲哨兵節點

  if (compareAndSetHead(new Node()))

  tail = head;

  } else {

  node.prev = t;

  if (compareAndSetTail(t, node)) {

  t.next = node;

  return t;

  }

  }

  }

  }

  也就是說,當隊列爲空時,在加入第一個節點的時候,會先加入一個哨兵節點。

  ReentrantLock 中的lock

  這裏分爲2中狀況分析lock()方法,以前說過ReentrantLock中的Sync有非公平和公平2中模式。又結合AQS的源碼分析,因此咱們知道區別在於FairSync 和NonfairSync 2個類中的 tryAcquire 實現不一樣。

  FairSync 公平狀況以下:

  protected final boolean tryAcquire(int acquires) {

  final Thread current = Thread.currentThread();

  int c = getState();

  //當state爲0時,就標識空閒,能夠被獲取

  if (c == 0) {

  //hasQueuedPredecessors是公平策略,

  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;

  }

  能夠看到對state的修改都是運行了CAS來更新,同時,在修改state的基礎上,使用了公平策略hasQueuedPredecessors是在AQS中的方法

  //判斷當前節點是不是隊列的第一個節點,若是當前節點有前驅結點返回true,當前隊列爲空或者當前節點是第一個節點則返回false

  public final boolean hasQueuedPredecessors() {

  Node t = tail;

  Node h = head;

  Node s;

  return h != t &&

  ((s = h.next) == null || s.thread != Thread.currentThread());

  }

  這裏最後的判斷能夠分開來看,h==s則是當前隊列爲空,直接返回false,若是h!=s且s==null則是,說明有一個元素將要做爲AQS的第一個節點入隊列,那麼返回true,若是,s.thread != Thread.currentThread()) 就表明,第一個元素就不是當前的線程,返回true。

  NonfairSync 非公平狀況以下鄭州不孕不育醫院:http://www.zzfkyy120.com/

  protected final boolean tryAcquire(int acquires) {

  return nonfairTryAcquire(acquires);

  }

  //非公平獲取

  final boolean nonfairTryAcquire(int acquires) {

  final Thread current = Thread.currentThread();

  int c = getState();

  if (c == 0) {

  //缺乏了校驗公平的策略,直接對state進行修改

  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;

  }

  因此按照以上Lock的邏輯,lockInterruptibly即是調用了acquireInterruptibly的方法來獲取資源

  public final void acquireInterruptibly(int arg)

  throws InterruptedException {

  //判斷線程的狀態

  if (Thread.interrupted())

  throw new InterruptedException();

  if (!tryAcquire(arg))

  doAcquireInterruptibly(arg);

  }

  ReentrantLock 中的unlock

  ReentrantLock 中的unlock,是沒有策略之分,在Snyc中就實現了方法

  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;

  }

  unlock的代碼就比較好讀了,先判斷lock的持有線程與當前線程是否一致,而後是的話,就把state和持有線程清空。最後在AQS中,會移除該節點

  固然在AQS中,還存在一個條件隊列,在後續文章再談。

  下面是ReentrantLock 的一個例子,模擬領取優惠卷的狀況

  public class ReenTrantLockDemo extends Thread{

  //模擬優惠卷

  private static List array = new ArrayList<>();

  private static Lock lock = new ReentrantLock();

  public Integer get(){

  lock.lock();

  try {

  Integer o= array.get(0);

  array.remove(o);

  return o;

  }catch (Exception e){

  System.out.println("獲取出錯");

  }finally {

  lock.unlock();

  }

  return -1;

  }

  @Override

  public void run() {

  Integer a = get();

  System.out.println("獲取到的優惠卷編號爲"+a);

  }

  public static void main(String[] args) {

  for(int i=0;i<10;i++){

  array.add(i);

  }

  ReenTrantLockDemo reenTrantLockDemo = new ReenTrantLockDemo();

  ExecutorService service = Executors.newCachedThreadPool();

  ReenTrantLockDemo demo = new ReenTrantLockDemo();

  for (int i = 0; i < 10; i++) {

  service.submit(demo);

  }

  service.shutdown();

  }

  }

  結果爲

  獲取到的優惠卷編號爲3

  獲取到的優惠卷編號爲2

  獲取到的優惠卷編號爲6

  獲取到的優惠卷編號爲5

  獲取到的優惠卷編號爲1

  獲取到的優惠卷編號爲0

  獲取到的優惠卷編號爲4

  獲取到的優惠卷編號爲8

  獲取到的優惠卷編號爲7

  獲取到的優惠卷編號爲9

  10

  去掉lock的結果爲:

  獲取到的優惠卷編號爲0

  獲取到的優惠卷編號爲5

  獲取到的優惠卷編號爲0

  獲取到的優惠卷編號爲0

  獲取到的優惠卷編號爲0

  獲取到的優惠卷編號爲7

  獲取到的優惠卷編號爲4

  獲取到的優惠卷編號爲8

  獲取到的優惠卷編號爲0

  獲取到的優惠卷編號爲6

  名詞解釋

  公平鎖與非公平鎖 :是否按照線程進入阻塞隊列的順序來執行

相關文章
相關標籤/搜索