Java并发编程:AbstractQueuedSynchronizer之ReentrantLock源码分析1.8

1、 ReentrantLock的结构:
ReentrantLock的底层是借助AbstractQueuedSynchronizer实现,所以其数据结构依附于AbstractQueuedSynchronizer的数据结构。在这里插入图片描述
ReentrantLock实现Lock接口,Sync与ReentrantLock是组合关系,且FairSync(公平锁)、NonfairySync(非公平锁)是Sync的子类。Sync继承AQS(AbstractQueuedSynchronizer)。
AQS(AbstractQueuedSynchronizer): 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()、setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。
CLH : AQS中“等待锁”的线程队列。在多线程环境中我们为了保护资源的安全性常使用锁将其保护起来,同一时刻只能有一个线程能够访问,其余线程则需要等待,CLH就是管理这些等待锁的队列。
CAS(compare and swap):比较并交换函数,它是原子操作函数,也就是说所有通过CAS操作的数据都是以原子方式进行的。

2、源码分析

2.1 类的继承

public class ReentrantLock implements Lock, java.io.Serializable

ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

2.2 类的内部类
ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类,如上图:

2.2.1 Sync:

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)) { // 比较并设置状态成功,状态0表示锁没有被占用
                    // 设置当前线程独占
                    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; 
        }
        
        // 判断资源是否被当前线程占有
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 新生一个条件
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class
        // 返回资源的占用线程
        final Thread getOwner() {        
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        // 返回状态
        final int getHoldCount() {            
            return isHeldExclusively() ? getState() : 0;
        }

        // 资源是否被占用
        final boolean isLocked() {        
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        // 自定义反序列化逻辑
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

Sync类存在如下方法和作用:
在这里插入图片描述

2.2.2 NonfairSync类:继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法

// 非公平锁
    static final class NonfairSync extends Sync {
        // 版本号
        private static final long serialVersionUID = 7316153563782823691L;

        // 获得锁
        final void lock() {
            if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
                // 把当前线程设置独占了锁
                setExclusiveOwnerThread(Thread.currentThread());
            else // 锁已经被占用,或者set失败
                // 以独占模式获取对象,忽略中断
                acquire(1); 
        }

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

2.2.3 FairSyn类:继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法

// 公平锁
    static final class FairSync extends Sync {
        // 版本序列化
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 以独占模式获取对象,忽略中断
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        // 尝试公平获取锁
        protected final boolean tryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            // 获取状态
            int c = getState();
            if (c == 0) { // 状态为0
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
                    // 设置当前线程独占
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
                // 下一个状态
                int nextc = c + acquires;
                if (nextc < 0) // 超过了int的表示范围
                    throw new Error("Maximum lock count exceeded");
                // 设置状态
                setState(nextc);
                return true;
            }
            return false;
        }
    }

跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。

2.3 类的属性

public class ReentrantLock implements Lock, java.io.Serializable {
    // 序列号
    private static final long serialVersionUID = 7373984872572414699L;    
    // 同步队列
    private final Sync sync;
}

2.4 类的构造函数
2.4.1 ReentrantLock()型构造函数:

public ReentrantLock() {
        // 默认非公平策略
        sync = new NonfairSync();
    }

可以看到默认是采用的非公平策略获取锁。

2.4.2 ReentrantLock(boolean)型构造函数:

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

可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略。

2.5 核心函数分析
2.5.1 获取非公平锁

final void lock() {
            if (compareAndSetState(0, 1))   //先去获取锁,获取失败,在执行下一步
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

1、首先通过CAS更新AQS中的状态变量来获得锁(第一次获得锁),如果获取成功则把当前线程设置为独占锁
2、如果失败,再去执行acquire方法

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

1、先执行的tryAcquire方法,尝试获得锁。
2、如果获取失败则进入addWaiter方法,构造同步节点,将该节点添加到同步队列尾部,并返回此节点,进入acquireQueued方法。
3、acquireQueued方法,这个新节点是循环的方式获取同步状态,如果获取不到则阻塞节点中的线程,阻塞后的节点等待前驱节点来唤醒。

protected final boolean tryAcquire(int acquires) {
     return nonfairTryAcquire(acquires);  //可以看出这里直接调用的是nonfairTryAcquire方法
}
 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();  //获取到当前的线程
            int c = getState();    //获取锁的状态
            if (c == 0) {   //目前没有人在占有锁
                if (compareAndSetState(0, acquires)) {  //直接尝试把当前只设置成1,如果成功,把owner设置自己,并且推出
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {  
                int nextc = c + acquires;   //这里就是重入锁的概念,如果还是自己,则进行加1操作,因为释放和获取一定要是对等的
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;//当前锁被其他线程占用,退出。
        }

1、ryAcquire调用nonfairTryAcquire方法来第二次尝试获得锁
2、如果状态变量为0,则进行CAS尝试更新状态来获得锁,并把该线程设置成独占锁,并返回真。
3、如果状态变量不为0,则判断当前线程是否为独占锁,如果是,则当前状态+ 1(可重入锁),表示获取锁成功,更新状态值,并返回真。这里更新状态变量,不需要CAS更新,因为,当前线程已经获得锁。

说明:以上的源码大致和FairSync相同的,唯一不同的地方,就是当c==0的时候,公平锁需要去hasQueuedPredecessors,去判断一下是否有前驱节点,在判断是否要去设置,获取锁。

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

将构造的同步节点加入到同步队列中
1、使用链表的方式把该节点节点添加到队列尾部,如果尾的前驱节点不为空(队列不为空),则进行CAS添加到队列尾部。
2、如果更新失败(存在并发竞争更新),则进入ENQ方法进行添加

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

该方法使用CAS自旋的方式来保证向队列中添加节点
1、如果队列为空,则把当前节点设置成头节点
2、如果队列不为空,则向队列尾部添加节点

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

在acquireQueued方法中,当前线程在死循环中尝试获取同步状态:
1、如果当前节点的前驱节点头节点才能尝试获得锁,如果获得成功,则把当前线程设置成头结点,把之前的头结点从队列中移除,等待垃圾回收(没有对象引用)
2、如果获取锁失败则进入shouldParkAfterFailedAcquire方法中检测当前节点是否可以被安全的挂起(阻塞),如果可以安全挂起则进入parkAndCheckInterrupt方法,把当前线程挂起,并检查刚线程是否执行了中断方法。

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 {
            /*
             * 尝试将当前节点的前驱节点的等待状态设为SIGNAL
             * 1/这为什么用CAS,现在已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操作这个节点了,那为什么还要用CAS?而不直接赋值呢?
             * (解释:因为pred可以自己将自己的状态改为cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操作)
             * 2/既然前驱节点已经设为SIGNAL了,为什么最后还要返回false
             * (因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了)
             * (网上摘抄的,解释的很明白)
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

1、首先获取前驱节点的等待状态WS
2、如果WS为SIGNAL则表示可以被前驱节点唤醒,当前线程就可以挂起,等待前驱节点唤醒,返回真(可以挂起)
3、如果WS> 0说明,前驱节点取消了,并循环查找此前驱节点之前所有连续取消的节点。并返回假(不能挂起)。
4、尝试将当前节点的前驱节点的等待状态设为信号

waitStatus状态值:
取消 1 等待超时或者中断,需要从同步队列中取消
信号 -1 后继节点出于等待状态,当前节点释放锁后将会唤醒后继节点
条件 -2 节点在等待队列中,节点线程等待在条件上,其它线程对条件调用信号()方法后,该节点将会从等待同步队列中移到同步队列中,然后等待获取锁。
传播 -3 表示下一次共享式同步状态获取将会无条件地传播下去
初始 0 初始状态

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

把当前线程挂起,并检查刚线程是否执行了中断方法,并返回真,假。

2.5.2 释放非公平锁

public void unlock() {
        sync.release(1);
    }
    
 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;   //获取当前的锁的状态并且减1,因为要释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())  //如果当前自己不是锁的持有者,只有自己才能释放锁
                throw new IllegalMonitorStateException();
            boolean free = false;  
            if (c == 0) {  //释放成功
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c); //重新设置成状态
            return free;

释放锁成功后,获取头节点,接着唤醒后继节点,调用unparkSuccessor方法:

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;  //获取头结点的等待状态
        if (ws < 0)   //把该状态设置成0
            compareAndSetWaitStatus(node, ws, 0);
 
        Node s = node.next;  //找到后继节点,唤醒后继节点
        if (s == null || s.waitStatus > 0) {  //很不巧,后继节点,节点为null,或者被取消
            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);  //唤醒节点

AQS中维护的两个队列:
在这里插入图片描述
1、Condition队列,表示等待的队列,其waitStatus=Node.Condition,由firstWaiter和lastWaiter两个属性操控.
2、Sync队列,表示可以竞争锁的队列,这个跟AQS一致,waitStatus=0;
3、await()方法就是把当前线程创建一个Node加入Condition队列,接着就一致循环查其在不在Sync队列,如果当前节点在Sync队列里了,就可以竞争锁,恢复运行了.
4、signal()方法就是把某个节点的nextWaiter设为null,再把其从Condition队列转到Sync队列.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值