Understanding the underlying source depth ReentrantLock

table of Contents

  • About ReentrantLock

  • Bedding basics

    • state property
    • Thread holder of property
    • Use of the queue ReentrantLock
  • Demo & analytical principle

    • Fair lock -lock () method

    • Demo

    • Vernacular principle (oral interview)

    • Detailed principles

    • Knowledge summary

      • FIFO list summarizes the life trajectory

      • waitStatus property life trajectory summary
    • unLock () method

    • Vernacular principle (oral interview)
    • Detailed principles

    • Fair lock -lock () method

    • Demo
    • Vernacular principle (oral interview)
    • Detailed principles

    • lockInterruptibly()方法

    • Demo
    • Vernacular principle (oral interview)
    • Detailed principles

    • tryLock () method

    • Demo
    • Vernacular principle (oral interview)
    • Detailed principles

    • tryLock(long timeout, TimeUnit unit)方法

    • Demo
    • Vernacular principle (oral interview)
    • Detailed principles
  • Face questions Summary

  • ReentrantLock whole Chinese comments can run code download

About ReentrantLock

jdk and contracting the reentrant lock, based AQS (AbstractQueuedSynchronized) implementation, which have a fair lock (order in a thread locked based solely on the sequence of the calling method) and unfair lock (lock incomplete order in a thread based on the sequence of method call) implemented in two ways.

ReentrantLock offers a variety of API: Lock fair / unfair lock -lock () method, time-release lock -tryLock (long timeout, TimeUnit unit) method, interrupt interrupt blocking thread throws InterruptedException anomaly, failure to obtain a lock tryLock direct return ( )

Based on the open-jdk 1.8 explain

Bedding basics

state property

    //同步状态标识    
    private volatile int state;

state is 0, there is no representative of the current thread holds the lock; is 1, representing a thread holds the lock; if more than 1, since the lock ReentrantLock reentrant, so the number representative of the current thread lock is reentrant.

The use of volatile, to ensure the visibility of the state property value (that is, the visibility at the time of obtaining property values, always guaranteed to be the latest characteristic value).

Thread holder of property

    //当前线程持有者标识
    private transient Thread exclusiveOwnerThread;

This property is of type Thread, identifies the current thread holding the lock.

Use of the queue ReentrantLock

ReentrantLock in the queue is FIFO (First In First Out), in order to achieve a fair locks, lock order to ensure that the thread of the thread, but also storage element, then the queue data structure is what is it?

In ReentrantLock, the definition of a Node class that represents a thread, but also a constituent element of the list, the prev Node attribute points to a front node (a representative of the top thread queue), a node (representing the attribute points to the next a thread into the queue), a linked list is formed in this way; also maintains the AQS node type head and tail attributes and defaults to null, respectively, the head node and tail node, these two properties in order to allow subsequent logic , it is possible to easily get the head and tail nodes, and logical processing to make judgment.

The following are the core attributes of the Node class:

    //指向前一个节点   
    volatile Node prev; 

    //指向后一个节点   
    volatile Node next; 

    //指向当前节点表示线程    
    volatile Thread thread; 

    /*
        等待状态,针对 ReentrantLock 下的方法,共有3种值,为什么说是针对呢?
      因为该属性是继承AQS而来的,其它并发包也在使用这个属性,所以ReentrantLock只有用到部分
        针对 ReentrantLock 都有什么值呢,都是什么含义呢?
      0:初始化默认状态或者是无效状态,即在成员变量定义int类型默认为0,或者表示已解锁
      -1(SIGNAL):标记当前结点表示的线程在释放锁后需要唤醒下一个节点的线程,以当前值来标识是否要进行唤醒操作
      1(CANCELLED):在同步队列中等待的线程未正常结束(发生中断异常或者其它不可预知的异常),标记为取消状态
     */
    volatile int waitStatus; 

Demo & analytical principle

Unfair lock -lock () method

Demo

    public void testReentrantLock() {    
     //多个线程使用同一个ReentrantLock对象,上同一把锁
      Lock lock = new ReentrantLock();      
      lock.lock();  
      System.out.println("处理");
      lock.unlock();    
    }

Vernacular principle (oral interview)

Call ReentrantLock no-argument constructor initializes the default unfair locks realized; calls the lock method:

The first step, to seize the lock

  1. Into the lock on the first method invocation cas method seize lock (to modify the state from 0 to 1), regardless of whether there is a thread in the queue

    1. If the modification is successful, the property owner to update the current thread is the current thread

    2. If the modification is unsuccessful, it is determined that the current thread is not the holder of the current thread, but before that there might be other thread releases the lock, the state becomes 0, so still have to determine what the value of the state of

      1. If it is 0, then call the method attempts to lock cas, regardless of whether there is a thread in the queue

        1. Lock is successful, the property owner to modify the current thread returns successfully unlocked
        2. If the lock fails, then join the queue to enter the process
      2. If the state is not 0, it is determined whether or not the holder of the thread is the current thread

        1. If the current thread, the state plus one, tired aggravate the frequency locked successful return
        2. If the current thread, then join the queue to enter the process

The second step, to seize the lock fails, join the queue

  1. Initialization represents the current thread Node Node node, by determining whether the end node is null manner, determines whether the list is initialized
    1. If the list is not initialized, a construct does not represent any thread Node Type node as the first node, and calls the method cas assigned to variables head and tail, only this time a node list node, i.e., the head node is tail node
    2. If the list has been initialized, next node in the attribute assignment prev attribute assigned to the node before the tail of the linked list, the linked list until the tail node is node node, then the node is tail node variable assignment

The third step, after the queued spin 1-2 attempts to acquire the lock, if we can not get a lock, the thread is blocked until it is awakened, successfully acquired lock

  1. Analyzing prev node attributes of nodes (previous node) whether the variable head (the head node)
    1. If it is the first node, then try again to acquire the lock
      1. If the lock acquisition is successful, the current head node to variable node (head node), and to quickly old GC of the first node, the next property assignment old head node is null
      2. If you acquire the lock is not successful, try to block the thread
  2. If it is not the first node fails to acquire the lock or, using the method of the cas prev node node node waitStatus from the default values ​​0-1, the thread to the thread of the current node represents needs to wake up after releasing the lock of a node
  3. Then when node node node waitStatus prev value of -1, the calls LockSupport.park (this) method of the current thread is blocked (the blocking time is not blocked), the unlocking operation can be invoked by blocking the thread or by a node the current thread calls interrupt method to wake up (here we note that, even before the node is awakened, it may achieve logic unfair because the lock, after the wake, is acquiring the lock other threads), after the wake-up method if you use interrupt the current interrupt status temporarily clear, once again spins third step logic
  4. After successfully locked, and then restore the interrupt status
  5. Locking successful return

interrupt (); call interrupt another thread in a thread () method, the thread will only interrupt status is set to the thread, the thread will not really interrupt, but to determine whether the interruption in the state, developers define their own how to deal with in break thread, in general, will throw an exception InterruptedException

isInterrupted (boolean ClearInterrupted); return is in the interrupt status, ClearInterrupted is true, it will clear the interrupt status, otherwise reserved interrupt status

Detailed principles

Before reading detailed principles for strengthening understanding, please read the source code control (included with the end of this Chinese comments can run the code download link)

After entering the lock method, on the first call cas method seize lock (modify the state from 0 to 1), regardless of whether there is a thread in the queue; if the modification is successful, then the property owner to update the current thread is the current thread, if the modification is unsuccessful then call acquire method;

    final void lock() {    
        //直接使用cas原子方法,如果state为0则修改为1,而不乖乖去FIFO(先进先出)队列排队    
        if (compareAndSetState(0, 1))        
            //如果上锁成功,将锁持有者修改为当前线程对象,上锁成功              
            setExclusiveOwnerThread(Thread.currentThread());   
        else        
            //如果失败,则执行以下逻辑(包括判断是否是线程重入、再次尝试上锁、阻塞线程、被唤醒获取锁等逻辑)   
           acquire(1);
    }

After entering acquire method, and then attempt to acquire the lock, if the acquisition fails to initialize on behalf of node current thread, added to the queue to go and block them, until they were awakened (reason here to try to get twice the action, in order to give full play to the non lock fair performance advantages)

    public final void acquire(int arg) {
        // 1.tryAcquire: 再次尝试上锁,true:标识上锁成功;false:标识上锁失败
        if (!tryAcquire(arg) &&
                /*
                 2.addWaiter:将线程封装成节点,放入FIFO队列中;
                 3.acquireQueued:
                 自旋获取锁,如果再次尝试获取锁失败,则阻塞线程;
                 等待队列中的前一个线程解锁后,唤醒本线程,成功获取锁
                 */
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //将线程的状态置为中断(该代码的作用在acquireQueued里讲)
            selfInterrupt();
    }

After entering 1.tryAcquire, if this time the wireless process holding the lock, then try again to acquire the lock: 1 if the acquisition fails, enter the queue; 2 otherwise succeed; if there is a thread that holds the lock, it is determined whether the present. the thread that holds the lock: 1 if yes, the number of aggravated tired; 2 If not, enter the queue.

    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) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //当前锁的持有者正是当前线程,则累加重入数,返回上锁成功
        else if (current == getExclusiveOwnerThread()) {
            //之所以使用不支持原子性的操作进行赋值,是因为只有当前拥有锁的线程才能修改这个state,所以不会发生其他线程修改
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

After entering 2.addWaiter, create a node on behalf of the current thread and the list appended.

    /**
     * 描述:初始化节点并追加链表末尾
     *
     * @param mode 标识当前线程的节点是独占模式还是共享模式,对代码逻辑没有实际意义
     * @return 代表当前线程的节点
     */
    private Node addWaiter(Node mode) {
        //初始化链表中的对象NOde,代表当前线程
        Node node = new Node(Thread.currentThread(), mode);

        Node pred = tail;
        //如果链表已经初始化(最后一个节点不为空),则直接将当前节点放在尾节点的后面接口
        if (pred != null) {
            //将node的prev属性赋值为之前链表的尾结点
            node.prev = pred;
            //  使用原子方法,tail变量赋值成node节点
            // (注:这里只修改了记录了尾节点的变量,并没有修改链表节点间的关联关系)
            if (compareAndSetTail(pred, node)) {//#1
                //将之前链表的尾结点的next属性赋值为node节点(这里之所以没有使用cas原子方法是因为其它线程想要修改t的next属性都必须成功获取t,而t是只有在t所代表节点是尾节点的那个时间点,成功执行compareAndSetTail(t, node)的线程才能够拥有的)
                pred.next = node;
                return node;
            }
        }
        //如果链表还没有初始化或者因为锁竞争激烈,#1处的compareAndSetTail执行失败,将会对链表进行初始化或者自旋直到compareAndSetTail执行成功
        enq(node);
        return node;
    }
    /**
     * 如果链表还没有初始化或者因为锁竞争激烈,#1处的compareAndSetTail执行失败,将会对链表进行初始化或者 自旋 直到compareAndSetTail执行成功
     */
    private Node enq(final Node node) {
        for (; ; ) {
            Node t = tail;
        //如果链表未初始化(尾节点变量为空),则需要初始化
        if (t == null) { // Must initialize #2
            //初始化节点(不代表任何线程),作为head变量
            if (compareAndSetHead(new Node()))
                //只有一个节点的链表,头就是尾
                tail = head;
        } else {
            //有两种情况会到达这:
            // 1、链表已经在其它线程初始化,但是在本线程竞争执行compareAndSetTail失败了
            // 2、当前线程在执行addWaiter方法时,链表还未初始化,但当执行上面的#2代码时被其它线程初始化了
            //将node的prev属性赋值为之前链表的尾结点
            node.prev = t;
            //再次尝试修改tail变量为当前线程节点,自旋尝试变更,直到成功为止
            if (compareAndSetTail(t, node)) {
                // 将之前链表的尾结点的next属性赋值为node节点
                t.next = node;
                return t;
            }
        }
    }

After entering 3.acquireQueued, spin 1-2 attempts to acquire the lock, if we can not get a lock, the thread is blocked, until they were awakened successfully acquired lock


  • The remaining 70% of the content, add my micro letter, after sending 49.9 yuan red envelope to see if the content has more than meet your needs and appreciates even try yo!
  • If there is no clear description of technical blind spots or other issues, let me know, will be revised
  • After the pay to read the source code to explain if found wrong, tell me, is the case, the revised article, 10% cash back / Articles
  • Personal E-mail: [email protected], when you add a micro-letters, notes, text captions
    Understanding the underlying source depth ReentrantLock
    Understanding the underlying source depth ReentrantLock

Guess you like

Origin blog.51cto.com/14528022/2437550