[并发编程]------死肝ReentrantLock源码

目录

1.ReentrantLock特性

2.AbstractQueuedSynchronizer与Node

2.1AbstractQueuedSynchronizer中有四个重要的参数

2.2Node中有四个重要的参数

3.ReentrantLock公平锁FairSync

3.1reentrantLock.lock

3.1.1tryAcquire尝试获取锁

3.1.2acquireQueued入CHL队列

3.1.3阻塞 

3.2reentrantLock.unlock


1.ReentrantLock特性

(1)支持公平锁和非公平锁

(2)可重入

(3)手动加锁、解锁

2.AbstractQueuedSynchronizer与Node

[并发编程]------死肝ReentrantLock源码_第1张图片

2.1AbstractQueuedSynchronizer中有四个重要的参数

(1)state:初始值为0,表示未有线程持有锁

(2)exclusiveOwnerThread:当前持有锁的线程

(3)head:CLH队列头节点,本质上一个Node

(4)tail:CLH队列尾节点,本质上一个Node

2.2Node中有四个重要的参数

(1)prev:前驱结点

(2)next:后继结点

(3)thread:当前阻塞的等待线程

(4)waitState:Node结点状态值,总共有五种状态

状态 对应值 描述
SIGNAL -1 处于SIGNAL状态的结点的后继结点可被唤醒
CANCELLED 1 处于CANCELLED状态的结点已被中断取消,永远不会再被阻塞
CONDITION -2 表示处于CONDITION状态的结点在等待队列中
PROPAGATE -3 传播
DEFAULT 0 初始化结点的默认值

3.ReentrantLock公平锁FairSync

    public static void main(String[] args)  {
        ReentrantLock reentrantLock = new ReentrantLock(true);
        reentrantLock.lock();
        try{
            /**
             * 业务逻辑
             */
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }

3.1reentrantLock.lock

调用的是FairSync的lock方法

    public void lock() {
        //调用的是FairSync的lock方法
        sync.lock();
    }

lock中调用AbstractQueuedSynchronizer的acquire方法

    final void lock() {
        acquire(1);
    }

acquire方法中主要做三件事

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

3.1.1tryAcquire尝试获取锁

调用FairSync的tryAcquire方法

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            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;
    }

情景一:假设当前只有t0线程尝试获取锁,则AQS和Node的变化示意图如下:

[并发编程]------死肝ReentrantLock源码_第2张图片

首次进来的时候,AQS的state的值为0,此时进入hasQueuedPredecessors判断CHL队列中是否有在等待获取锁的线程(因为公平锁,需要判断CHL队列中是否有等待线程,没有的话,t0才可以获取锁,否则需要加入CHL队列)。

如何判断CHL队列是否有等待获取锁的线程呢?

如果head != tail 且(h.next == null || t0 != h.next.thread) ,则CHL队列中有线程在排队等待获取锁

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

首次进来的时候,head和tail都为null,所以hasQueuedPredecessors为false,此时则通过CAS操作将state的值从0修改为1,若修改成功,则将AQS的exclusiveOwnerThread = t0。至此,t0获取到锁。

情景二:假设在t0已经获取到锁的情况下,t0继续调用lock()方法,会加锁成功吗?

[并发编程]------死肝ReentrantLock源码_第3张图片

当t0第一次获取锁的时候,AQS的state已经被修改为1,则此时tryAcquire会进入第二个判断,判断当前进来的线程,是否与AQS中持有锁的线程一致,若一致,则对AQS的state+1。此时修改AQS的值没有使用CAS是因为不存在线程竞争,所以只需要简单的赋值即可。这也是ReentrantLock是可重入锁的依据。

3.1.2acquireQueued入CHL队列

假设在t0已经获取到锁的情况下,t1也来尝试获取锁,会发生什么情况呢?

此时由于t0已经获取到锁,所以当t1执行tryAcquire会返回false,则接着执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),该方法目的就是将t1加入CHL等待队列中,阻塞t1,等待被唤醒

    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;
    }

首先为t1构造成一个Node对象,node对象中的thread=t1。此时由于tail为null,所以pred == null,所以会进入enq(node)方法。

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

enq方法中最外层是一个无限循环,第一次循环的时候,tail == null,所以会使用CAS初始化AQS的head结点,若CAS成功,则head结点和tail结点都同时指向一个Node对象。 

当head和tail都初始化之后, 将装载t1的Node对象t的前驱结点指向tail,再将tail指向t,然后再将原来的tail对象的next指向t,从而让t入队CHL

这次为什么在最外层需要无限循环呢?

1.保证初始化head和tail的时候,CAS一定能够成功

2.保证需要等待获取锁的线程的Node一定能够入队成功 

[并发编程]------死肝ReentrantLock源码_第4张图片 

 

当装载t1线程的Node对象t入队之后,接着调用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);
        }
    }

此时t的前驱结点为head,且不为null,则会再尝试获取一次锁,即再一次调用tryAcquire方法。目的是作者觉得若此时t0释放锁了,则t1就可以不阻塞直接获取到锁。

情景一:若此时t1获取锁成功,则AQS和Node的变化示意图如下:

[并发编程]------死肝ReentrantLock源码_第5张图片

 此时会将AQS的exclusiveOwnerThread修改为t1,将t的前驱结点出队,将t的thread置为null,并将AQS的head和tail都指向t(此时t相当于又变成了初始化的Node对象)

情景二:若此时t1获取锁失败,则AQS和Node的变化示意图如下:

[并发编程]------死肝ReentrantLock源码_第6张图片

此时调用shouldParkAfterFailedAcquire,该方法配合最外层的无限循环,最终会将t对象的前驱结点的waitState的值从0修改为-1(SIGNAL),本质就是自旋+CAS

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  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, ws, Node.SIGNAL);
        }
        return false;
    }

 3.1.3阻塞 

将前驱结点的waitState修改为-1后,会调用parkAndCheckInterrupt,实现线程阻塞。当线程被唤醒时,会调用Thread.interrupted()清除中断标志。


    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

本质就是使用LockSupport.park来阻塞线程,此时线程会一直阻塞,直至有人唤醒。

这里有两个疑问

1)为什么要将前驱结点的waitState从0修改为-1?

2)线程什么时候被唤醒?

我们可以带着疑问来看看reentrantLock是如何解锁的。

3.2reentrantLock.unlock

当线程调用unlock时,最终会调用release方法

    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方法尝试解锁,获取AQS的state值并减1。判断当前解锁的线程是否为AQS中获取锁的线程(只能解锁自己拥有的锁),若此时AQS的state-1之后为0,则相当于当前线程已经释放了锁,将AQS的exclusiveOwnerThread置为null,并将AQS的值修改为state-1。否则,说明当前释放锁的线程还只有锁。

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;
    }

 当线程完全释放锁之后(state=0),判断CHL队列中是否有在等待唤醒的线程,若有,则判断head的waitstate是否!=0(前面加锁之后,已经将head的waitState修改为-1),所以此时waitState必定不等于0,则会调用unparkSuccessor方法。

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

该方法会将head的waitState从-1修改为0,并且唤醒head的后继结点,调用LockSupport.unpark(s.thread)唤醒(前面阻塞的线程,在此处会被唤醒) 

你可能感兴趣的:(java,jvm,开发语言)