`
森林的天空
  • 浏览: 15248 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

ReentrantLock源码之一lock方法解析(锁的获取)

 
阅读更多

一、前言
    ReentrantLock是JDK1.5引入的,它拥有与synchronized相同的并发性和内存语义,并提供了超出synchonized的其他高级功能(例如,中断锁等候、条件变量等),并且使用ReentrantLock比synchronized能获得更好的可伸缩性。

    ReentrantLock的实现基于AQS(AbstractQueuedSynchronizer)和LockSupport。
    AQS主要利用硬件原语指令(CAS compare-and-swap),来实现轻量级多线程同步机制,并且不会引起CPU上文切换和调度,同时提供内存可见性和原子化更新保证(线程安全的三要素:原子性、可见性、顺序性)。

    AQS的本质上是一个同步器/阻塞锁的基础框架,其作用主要是提供加锁、释放锁,并在内部维护一个FIFO等待队列,用于存储由于锁竞争而阻塞的线程。

二、关键代码分析

1.关键字段

    AQS使用链表作为队列,使用volatile变量state,作为锁状态标识位。

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->/** *//**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     
*/

    
private transient volatile Node head;       //等待队列的头

    
/** *//**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     
*/

    
private transient volatile Node tail; //等待队列的尾

    
/** *//**
     * The synchronization state.
     
*/

    
private volatile int state;             //原子性的锁状态位,ReentrantLock对该字段的调用是通过原子操作compareAndSetState进行的


   
protected final boolean compareAndSetState(int expect, int update) {
        
// See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
   }




2.ReentrantLock的公平锁与非公平锁


   从ReentrantLock的构造子可以看到,ReentrantLock提供两种锁:公平锁和非公平锁,其内部实现了两种同步器NonfairSync、FairSync派生自AQS,主要才采用了模板方法模式,主要重写了AQS的tryAcquire、lock方法,如下图。

  

   

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->public ReentrantLock() {
         sync 
= new NonfairSync();
 }


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


    

3.获取锁操作

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

由于NonfairSync、FairSync分别实现了lock方法,我们将分别探讨

3.1NonfairSync.lock()分析

 (1)通过原子的比较并设置操作,如果成功设置,说明锁是空闲的,当前线程获得锁,并把当前线程设置为锁拥有者;
 (2)否则,调用acquire方法;
 

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->package java.util.concurrent.locks.ReentrantLock;
        
final void lock() {
            
if (compareAndSetState(01))//表示如果当前state=0,那么设置state=1,并返回true;否则返回false。由于未等待,所以线程不需加入到等待队列
                setExclusiveOwnerThread(Thread.currentThread());
            
else
                acquire(
1);
        }

 
 
package java.util.concurrent.locks.AbstractOwnableSynchronizer  //AbstractOwnableSynchronizer是AQS的父类
 protected final void setExclusiveOwnerThread(Thread t) {
            exclusiveOwnerThread 
= t;
        }

 
3.1.1acquire方法分析
 (1)如果尝试以独占的方式获得锁失败,那么就把当前线程封装为一个Node,加入到等待队列中;如果加入队列成功,接下来检查当前线程的节点是否应该等待(挂起),如果当前线程所处节点的前一节点的等待状态小于0,则通过LockSupport挂起当前线程;无论线程是否被挂起,或者挂起后被激活,都应该返回当前线程的中断状态,如果处于中断状态,需要中断当前线程。
 

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->package java.util.concurrent.locks.AbstractQueuedSynchronizer
 
public final void acquire(int arg) {
         
if (!tryAcquire(arg) &&
              acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
             selfInterrupt();
     }


 
  
protected final boolean tryAcquire(int acquires) {
            
return nonfairTryAcquire(acquires);
        }


 3.1.2nonfairTryAcquire分析

 (1)如果锁状态空闲(state=0),且通过原子的比较并设置操作,那么当前线程获得锁,并把当前线程设置为锁拥有者;
 (2)如果锁状态空闲,且原子的比较并设置操作失败,那么返回false,说明尝试获得锁失败;
 (3)否则,检查当前线程与锁拥有者线程是否相等(表示一个线程已经获得该锁,再次要求该锁,这种情况叫可重入锁),如果相等,维护锁状态,并返回true;
 (4)如果不是以上情况,说明锁已经被其他的线程持有,直接返回false;
   

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->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()) {  //表示一个线程已经获得该锁,再次要求该锁(重入锁的由来),为状态位加acquires
                int nextc = c + acquires;
                
if (nextc < 0// overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                
return true;
            }

            
return false;
        }

3.1.3addWaiter分析
 (1)如果tail节点不为null,说明队列不为空,则把新节点加入到tail的后面,返回当前节点,否则进入enq进行处理(2);
 (2)如果tail节点为null,说明队列为空,需要建立一个虚拟的头节点,并把封装了当前线程的节点设置为尾节点;另外一种情况的发生,是由于在(1)中的compareAndSetTail可能会出现失败,这里采用for的无限循环,是要保证当前线程能够正确进入等待队列;
 

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->package java.util.concurrent.locks.AbstractQueuedSynchronizer
   
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{  //如果当前队列不是空队列,则把新节点加入到tail的后面,返回当前节点,否则进入enq进行处理。
              node.prev = pred;
              
if (compareAndSetTail(pred, node)) {
                  pred.next 
= node;
                  
return node;
              }

         }

         enq(node);
         
return node;
     }


 
package java.util.concurrent.locks.AbstractQueuedSynchronizer
 
private Node enq(final Node node) {
        
for (;;) {
            Node t 
= tail;
            
if (t == null// tail节点为空,说明是空队列,初始化头节点,如果成功,返回头节点
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev 
= h;
                
if (compareAndSetHead(h)) {
                    tail 
= node;
                    
return h;
                }

            }

            
else {   //
                node.prev = t;
                
if (compareAndSetTail(t, node)) {
                    t.next 
= node;
                    
return t;
                }

            }

        }


3.1.4acquire分析
(1)如果当前节点是队列的头结点(如果第一个节点是虚拟节点,那么第二个节点实际上就是头结点了),就尝试在此获取锁tryAcquire(arg)。如果成功就将头结点设置为当前节点(不管第一个结点是否是虚拟节点),返回中断状态。否则进行(2)。 
(2)检测当前节点是否应该park()-"挂起的意思",如果应该park()就挂起当前线程并且返回当前线程中断状态。进行操作(1)。
  

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->  final boolean acquireQueued(final Node node, int arg) {
        
try {
            
boolean interrupted = false;
            
for (;;) {
                
final Node p = node.predecessor();
                
if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next 
= null// help GC
                    return interrupted;
                }

                
if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted 
= true;
            }

        }
 catch (RuntimeException ex) {
            cancelAcquire(node);
            
throw ex;
        }

     }


3.1.5 shouldParkAfterFailedAcquire分析

 (1)如果前一个节点的等待状态waitStatus<0,也就是前面的节点还没有获得到锁,那么返回true,表示当前节点(线程)就应该park()了。否则进行(2)。 
 (2)如果前一个节点的等待状态waitStatus>0,也就是前一个节点被CANCELLED了,那么就将前一个节点去掉,递归此操作直到所有前一个节点的waitStatus<=0,进行(4)。否则进行(3)。 
 (3)前一个节点等待状态waitStatus=0,修改前一个节点状态位为SINGAL,表示后面有节点等待你处理,需要根据它的等待状态来决定是否该park()。进行(4)。 
 (4)返回false,表示线程不应该park()。

 注意:一个Node节点可包含以下状态以及模式:
      /** waitStatus value to indicate thread has cancelled */    取消
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */   信号等待(在AQS中,是通过LockSupport进行线程间信号交互的)
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */       条件等待   
        static final int CONDITION = -2;
        /** Marker to indicate a node is waiting in shared mode */  共享模式
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */           独占模式
        static final Node EXCLUSIVE = null;

 

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->  private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        
int s = pred.waitStatus;
        
if (s < 0)
            
/**//*
             * This node has already set status asking a release
             * to signal it, so it can safely park
             
*/

            
return true;
        
if (s > 0{
            
/**//*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             
*/

     
do {
  node.prev 
= pred = pred.prev;
     }
 while (pred.waitStatus > 0);
     pred.next 
= node;
 }

        
else
            
/**//*
             * Indicate that we need a signal, but don't park yet. Caller
             * will need to retry to make sure it cannot acquire before
             * parking.
             
*/

            compareAndSetWaitStatus(pred, 
0, Node.SIGNAL);
        
return false;
    }


 
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(
this);  //阻塞,即挂起;在没有unpark之前,下面的代码将不会执行;
        return Thread.interrupted();//个人感觉,如果没有外部的interrupt或者超时等,这里将始终返回false;
    }


 
private static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }


3.2FairSync.lock()分析

 公平锁相对与非公平锁,在锁的获取实现上,差别只在FairSync提供自己的tryAcquire()的方法实现,代码如下:

 (1)如果锁状态为0,等待队列为空,或者给定的线程在队列的头部,那么该线程获得锁;
 (2)如果当前线程与锁持有者线程相等,这种情况属于锁重入,锁状态加上请求数;
 (3)以上两种情况都不是,返回false,说明尝试获得锁失败;

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->protected final boolean tryAcquire(int acquires) {
            
final Thread current = Thread.currentThread();
            
int c = getState();
            
if (c == 0{
                
if (isFirst(current) &&
                    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;
        }



    
final boolean isFirst(Thread current) {
        Node h, s;
        
return ((h = head) == null ||
                ((s 
= h.next) != null && s.thread == current) ||
                fullIsFirst(current)); 
//头为null,头的下个节点不是空且该节点的线程与当前线程是相等的,
    }


    
final boolean fullIsFirst(Thread current) {
        
// same idea as fullGetFirstQueuedThread
        Node h, s;
        Thread firstThread 
= null;//如果头不为空,且头的下个节点也不为空,且该节点的上一个节点是头节点,且该节点的线程不为null
        if (((h = head) != null && (s = h.next) != null &&
             s.prev 
== head && (firstThread = s.thread) != null))
            
return firstThread == current;
        Node t 
= tail;
        
while (t != null && t != head) {
            Thread tt 
= t.thread;
            
if (tt != null)
                firstThread 
= tt;
            t 
= t.prev;
        }

        
return firstThread == current || firstThread == null;
    }


 


4.总结

     ReentrantLock在采用非公平锁构造时,首先检查锁状态,如果锁可用,直接通过CAS设置成持有状态,且把当前线程设置为锁的拥有者。
如果当前锁已经被持有,那么接下来进行可重入检查,如果可重入,需要为锁状态加上请求数。如果不属于上面两种情况,那么说明锁是被其他线程持有,
当前线程应该放入等待队列。
     在放入等待队列的过程中,首先要检查队列是否为空队列,如果为空队列,需要创建虚拟的头节点,然后把对当前线程封装的节点加入到队列尾部。由于设置尾部节点采用了CAS,为了保证尾节点能够设置成功,这里采用了无限循环的方式,直到设置成功为止。
     在完成放入等待队列任务后,则需要维护节点的状态,以及及时清除处于Cancel状态的节点,以帮助垃圾收集器及时回收。如果当前节点之前的节点的等待状态小于1,说明当前节点之前的线程处于等待状态(挂起),那么当前节点的线程也应处于等待状态(挂起)。挂起的工作是由LockSupport类支持的,LockSupport通过JNI调用本地操作系统来完成挂起的任务(java中除了废弃的suspend等方法,没有其他的挂起操作)。
    在当前等待的线程,被唤起后,检查中断状态,如果处于中断状态,那么需要中断当前线程。

分享到:
评论

相关推荐

    Java并发之ReentrantLock类源码解析

    Java并发之ReentrantLock类源码解析 ReentrantLock是Java并发包中的一种同步工具,它可以实现可重入锁的功能。ReentrantLock类的源码分析对理解Java并发机制非常重要。本文将对ReentrantLock类的源码进行详细分析,...

    第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()1

    在深入源码之前,我们需要对获取锁的整体流程有一个大致的理解。ReentrantLock的`lock()`方法调用`Sync`的`lock()`,然后由`NonfairSync`或`FairSync`完成具体的锁获取逻辑。这个过程中涉及到的状态转换、线程阻塞...

    ReentrantLock源码分析

    ### ReentrantLock源码分析 #### 一、ReentrantLock简介 ReentrantLock是一个基于`AbstractQueuedSynchronizer`(AQS)实现的高级锁工具类。与传统的synchronized关键字相比,ReentrantLock提供了更多控制手段,比如...

    ReentrantLock源码的使用问题详解.docx

    Sync是ReentrantLock的抽象静态内部类,它扩展了AQS,并定义了获取锁的抽象方法lock()。FairSync和NonfairSync则分别实现了公平锁和非公平锁的逻辑。在FairSync中,获取锁时会检查队列中是否有其他线程等待,确保...

    ReentrantLock源码详解--条件锁

    ReentrantLock源码详解中最重要的一个部分就是条件锁,条件锁是指在获取锁之后发现当前业务场景自己无法处理,而需要等待某个条件的出现才可以继续处理时使用的一种锁。今天我们来详细介绍条件锁的原理和实现。 ...

    Java源码解析之可重入锁ReentrantLock

    Java源码解析之可重入锁ReentrantLock ReentrantLock是一个可重入锁,在ConcurrentHashMap中使用了ReentrantLock。它是一个可重入的排他锁,它和synchronized的方法和代码有着相同的行为和语义,但有更多的功能。 ...

    21 更高级的锁—深入解析Lock.pdf

    ReentrantLock提供了tryLock()方法,可以尝试获取锁而不阻塞。无参数版本会立即返回结果,有参数版本允许设置等待时间,超时未获取锁则返回false。 ```java if (lock.tryLock(2, TimeUnit.SECONDS)) { try { ...

    ReentrantLock源码解析(二)

    《ReentrantLock源码解析(二)》 在Java并发编程中,ReentrantLock是一个重要的同步工具类,它提供了一种比synchronized更细粒度的锁控制机制。本文主要探讨ReentrantLock的内部实现,特别是其数据结构和关键方法...

    基于JDK源码解析Java领域中的并发锁之设计与实现.pdf

    Lock接口提供了比synchronized关键字更灵活的锁操作,包括显式获取和释放锁、可中断锁等待、尝试获取锁等。典型的Lock实现如ReentrantLock,它支持公平锁和非公平锁,以及可重入和可中断的特性。 五、ReadWriteLock...

    基于JDK源码解析Java领域中的并发锁,我们需要特别关注哪些内容?

    在尝试获取锁时,公平锁遵循FIFO原则,而非公平锁则可能跳过等待队列中的线程。 3. **ReentrantReadWriteLock(读写锁)**: - **设计思想**:允许多个读取者同时访问,但只有一个写入者,提高并发性能。 - **...

    JUC知识点总结(三)ReentrantLock与ReentrantReadWriteLock源码解析

    尝试非阻塞地获取锁:tryLock(),调用方法后立刻返回; 能被中断地获取锁:lockInterruptibly():在锁的获取中可以中断当前线程 超时获取锁:tryLock(time,unit),超时返回 Condition 类和 Object 类锁方法区别区别 ...

    ReentrantLock与synchronized

    4. 超时等待:`tryLock()`方法允许设置超时时间,超过指定时间仍未获取锁,线程会返回,避免死锁。 5. 分离锁与条件:`ReentrantLock`可以创建多个条件对象,每个条件对应一个等待队列,使得线程等待和唤醒操作更加...

    ArrayBlockingQueue源码解析-动力节点共

    ArrayBlockingQueue是Java并发编程中一个重要的数据结构,它是Java并发包`java.util.concurrent`中的一个线程安全的阻塞队列。这个类基于数组实现,具有固定容量,并且提供了生产和消费元素的线程安全操作。本文将...

    7 AQS源码分析.docx

    例如,在`ReentrantLock`的实现中,`lock()`方法的非公平版本会尝试直接获取锁,如果失败则会进入队列等待。 对于锁的升级过程,Java的`synchronized`关键字在低并发情况下使用的是偏向锁,当只有一个线程访问同步...

    synchronized并发讲解源码.zip

    这里我们使用了一个对象(`lock`)作为监视器锁,只对包含`count++`操作的部分进行同步。这样可以提高程序的并发性能,因为其他不涉及共享数据的操作可以在多个线程间并行执行。 `synchronized`还有一种高级用法...

    系统解析JDK源码,领略大牛设计思想,JAVA进阶必备(2023新课,已完结)

    8. **多线程同步**:synchronized关键字、Lock接口及其实现如ReentrantLock,以及并发工具类如Atomic系列,源码解读能揭示其内部同步机制,提升多线程编程能力。 9. **字符串处理**:String类的不可变性、...

    Java多线程源码笔记.pdf

    ReentrantLock是Lock的一个实现,它支持锁的获取和释放,可以手动释放,具有更高的灵活性。 5. AQS(AbstractQueuedSynchronizer):AQS是一个抽象的队列同步器,它是Lock接口实现的基础,包括ReentrantLock、...

    Java并发编程:深入解析抽象队列同步器(AQS)及其在Lock中的应用

    维护资源状态的可用性最后,文档提供了AQS源码的初步分析,突出了其设计和实现的关键部分,如等待队列节点类Node的定义综合来看,文章为Java开发者提供了对AQS及其在ReentrantLock中应用的详细理解,是探索Java并发...

    精心整理的AQS和JUC相关的面试题.pdf【ReentrantLock】

    ⼀、ReentrantLock重⼊锁 1.1&gt; 概述 1.2&gt; 中断响应 lockInterruptibly() 1.3&gt; 锁申请等待限时 tryLock(long time, TimeUnit unit) 1.4&gt; 公平锁和⾮公平锁 1.5&gt; AQS源码解析 ⼆、Condition重⼊锁的搭配类 三、...

    多线程的入门 实例源码

    Lock接口提供了更细粒度的控制,如可重入性、公平性等特性,并支持显式获取和释放锁。 死锁是多线程编程中常见的问题,当两个或更多线程相互等待对方释放资源而形成僵局时,就会发生死锁。为了避免死锁,开发者应...

Global site tag (gtag.js) - Google Analytics