ReentrantLock源码分析(一)

一、AbstractQueuedSynchronizer类分析

        在学习ReentrantLock前,我们先了解一下AQS。AQS (AbstractQuenedSynchronizer)是抽象的队列式同步器。是多线程访问共享资源的同步器基础框架。

AQS是基于CLH队列锁实现的,用volatile修饰共享变量state,采用CAS(compareAndSwap)来修改state。state修改成功,则获得锁;修改失败,则进入CLH队列,等待被唤醒。

CLH(Craig,Landin,and Hagersten)队列: CLH队列是一个虚拟双向队列,即不存在队列实例,仅存在节点之间关联关系。

AQS的特点:

  • 可重入性
  • 公平/非公平性
  • 阻塞队列等待
  • 共享/独占
  • 允许中断

AQS定义了两种队列 

  • 同步等待队列:

  • 条件等待队列:

在这里插入图片描述二、ReentrantLock简介

ReentrantLock是一种基于AQS框架的应用实现,一种支持公平或非公平、可中断、可重入的锁。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。

1.类的继承关系

ReentrantLock实现Lock接口,Lock接口中常用的方法有lock()、unlock()以及newCondition(),

newCondition方法表示生成一个条件。

public class ReentrantLock implements Lock, java.io.Serializable 

Lock接口中的主要方法

2.类的内部结构

ReentrantLock有三个内部类,分别是Sync、NonfairSync、FairSync;两个构造方法

 

 类结构图

 说明:FairSync类和NonfairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。

3.类的构造函数
3.1无参构造函数ReentrantLock()

        默认采用非公平策略

public ReentrantLock() {
     //默认非公平策略
     sync = new NonfairSync();
}
3.2有参构造函数ReentrantLock(boolean fair)

        fair: true表示公平策略,false:表示非公平策略

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

三、ReentrantLock源码分析

public class ReentrantLockTest1 {
    private volatile static int count = 0;

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

#lock.lock实际调用的方法
public void lock() {
        sync.lock();
    }

Sync类的lock()方法是抽象方法,由子类FairSync和NonfairSync具体实现,我们先分析默认非公平锁NonfairSync的加锁过程

1. NonfairSync.lock
final void lock() {
            //cas抢占锁资源
            if (compareAndSetState(0, 1))
                //cas成功,设置当前线程为锁的占用者
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //cas失败,调用acquire(1)竞争锁
                acquire(1);
        }

    CAS原理

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

        compareAndSetState通过乐观锁的作比较并替换,表示当内存中的state值和预期expect值相等,则将state修改为update值。更新成功返回ture,否则返回false。

        state是AQS类中的一个属性,含义如下:

  • state=0,表示无锁状态
  • state>0,表示有线程获得了锁,此时state=1。由于ReentrantLock可重入,所以当同一个线程获取多次锁时,state会递增,比如重入5次,state=5。而在释放锁的时候,需要释放5次直到state=0,其他线程才资格获得锁。
2. AQS.acquire

acquire(int args)是AQS类的方法

 public final void acquire(int arg) {
        //1.尝试获取锁,获取成功返回true,获取失败,返回false
        //2.tryAcquire失败,通过addWaiter()方法将当前线程封装成Node添加到AQS队列尾部
        //3.acquireQueued方法将Node作为参数,通过自旋去尝试获取锁
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

1)NonfairSync.tryAcquire

NonfairSync重写 AQS 类中的 tryAcquire(),AQS 中 tryAcquire方法的定义,并没有实现,而是抛出异常。该方法的作用是尝试获取锁,获取成功,返回true,否则返回false。

#ASQS类中的tryAcquire方法
protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
#NonfairSync类中的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

NonfairSync类中的tryAcquire()方法调用了父类的nonfairTryAcquire()方法,个人觉得nonfairTryAcquire方法可以写在NonfairSync类中。

2)Sync.nonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取同步锁状态值state
            int c = getState();
            if (c == 0) {
                //无锁,使用CAS抢占所资源
                if (compareAndSetState(0, acquires)) {
                    //cas成功,设置当前线程为锁的占有者
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断当前线程是否为锁的占有者
            else if (current == getExclusiveOwnerThread()) {
                //增加重入次数
                int nextc = c + acquires;
                //超过int类型最大值
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

可以看到,nonfairTryAcquire()方法的加锁有以下几步:

a.获取当先指向的线程

b.获取state值,判读是否有锁,无锁,通过CAS抢占锁资源

c.有锁,判断当前线程是否为占有锁的线程,直接增加重入次数

d.获取锁成功,返回true,否则返回false

3. AQS.addWriter

当tryAcquire()方法获取锁失败后,通过addWaiter()方法将当前线程封装成Node添加到AQS队列尾部。入参mode表示当前节点的状态,传递的参数是Node.EXCLUSIVE,表示独占状态。这是AQS的独占锁功能。

 private Node addWaiter(Node mode) {
        //将当前线程封装成Node
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //tail为AQS队列尾节点,默认为null,将队列尾节点位置给临时节点pred
        Node pred = tail;
        //如果队列尾部节点tail不为空,说明队列中存在节点
        if (pred != null) {
            //将当前线程节点node的prev前继节点指向 尾节点tail
            node.prev = pred;
            //通过cas,把当前线程节点node设置为队列的尾节点,这样就加入到AQS队列尾部了
            if (compareAndSetTail(pred, node)) {
                //设置成功后,把原来尾节点的next后继节点指向node
                pred.next = node;
                return node;
            }
        }
        //队列尾节点为空,把node添加到AQS队列
        enq(node);
        return node;
    }

可以看到,addWriter()主要分为以下几步:

a.将当前线程封装成Node

b.判断AQS队列的尾节点tail是否为空,如果不为空,通过CAS把当前线程节点node设置为队列尾节点,加入到AQS队列尾部

c.如果AQS队列的尾节点tail为空,或者CAS失败,调用enq()方法将node节点添加到AQS队列

1)enq

enq()方法就是通过自旋操作把当前节点加入到队列中。

private Node enq(final Node node) {
        for (;;) {
            //获取尾节点
            Node t = tail;
            //尾节点为空,说明队列为空
            if (t == null) { // Must initialize
                //队列为空,通过CAS设置一个新创建的空节点为头节点,初始化队列
                if (compareAndSetHead(new Node()))
                    //尾节点也指向空节点
                    tail = head;
            } else {
                //尾节点不为空,将当前节点的prev前继节点指向尾节点
                node.prev = t;
                //通过CAS设置当前节点为尾节点
                if (compareAndSetTail(t, node)) {
                    //设置成功后,把原来尾节点的next指针指向当前节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

2)图解分析

4. AQS.acquireQueued

 通过前一步addWriter()将线程添加到队列后,会把入队的Node作为参数,传递给acquireQueued()作为入参去竞争锁。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取当前节点的prev节点
                final Node p = node.predecessor();
                //如果prev节点是头节点,说明有资格竞争锁
                if (p == head && tryAcquire(arg)) {
                    //获取锁成功,将当前节点设置为头节点
                    setHead(node);
                    //把原来的head节点从队列中移出
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //获取锁失败后,根据waitStatus判断是否要挂起线程,挂起后,判断线程是否要中断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                //最后,通过 cancelAcquire 取消获得锁的操作
                cancelAcquire(node);
        }
    }

1)获取当前节点的 prev 节点

2)如果 prev 节点为 head 节点,那么它就有资格去争抢锁,调用 tryAcquire 抢占锁

3)抢占锁成功以后,把获得锁的节点设置为 head,并且移除原来的初始化 head节点

4)如果获得锁失败,则根据 waitStatus 决定是否需要挂起线程

5)最后,通过 cancelAcquire 取消获得锁的操作

shouldParkAfterFailedAcquire()方法根据 waitStatus 决定是否需要挂起线程

#在同步队列中等待的线程等待超时或被中断
static final int CANCELLED =  1;
#只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程
static final int SIGNAL    = -1;
#和 Condition 有关系
static final int CONDITION = -2;
#共享模式下,PROPAGATE 状态的线程处于可运行状态
static final int PROPAGATE = -3;
# 0:初始状态
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前置节点的waitStatus
        int ws = pred.waitStatus;
        //如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            //返回 true,意味着可以被挂起
            return true;
        if (ws > 0) {
            //ws>0,表示prev节点取消了排队,直接移除这个节点
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                //相当于pred=pred.prev,node.prev=pred
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //从双向列表中移除 CANCELLED 的节点
            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.
             */
            //使用CAS设置prev节点的waitStatus为Node.SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt 挂起当前线程变成 WATING 状态

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

public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

四、锁的释放流程

1. unlock
public void unlock() {
        sync.release(1);
    }

在unlock()方法中会调用release(1)方法

public final boolean release(int arg) {
        //释放锁成功
        if (tryRelease(arg)) {
            //获取队列中的头节点
            Node h = head;
            //如果头节点不为空,并且waitStatus != 0
            if (h != null && h.waitStatus != 0)
                //调用unparkSuccessor(h)唤醒后续节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
2. tryRelease
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;
                //state=0,表示没有线程占用锁,将 Owner 线程设置为空
                setExclusiveOwnerThread(null);
            }
            //否则,修改state状态值
            setState(c);
            return free;
        }
3. unparkSuccessor
private void unparkSuccessor(Node node) {
        //获取head节点的waitStatus状态
        int ws = node.waitStatus;
        if (ws < 0)
            // 设置 head 节点状态为 0
            compareAndSetWaitStatus(node, ws, 0);

        //获取head节点的下一个节点
        Node s = node.next;
        //如果下一个节点为 null 或者 status>0 表示节点为cancelled 状态
        if (s == null || s.waitStatus > 0) {
            s = null;
            //通过从尾部节点开始扫描,找到距离 head 最近的一个waitStatus<=0 的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //next 节点不为空,直接唤醒这个线程即可
            LockSupport.unpark(s.thread);
    }

五、原本挂起的线程继续执行

通过 ReentrantLock.unlock,原本挂起的线程被唤醒以后继续执行,原来被挂起的线程是在 acquireQueued 方法中,所以被唤醒以后继续从这个方法开始执行。

六、公平锁和非公平锁的区别

锁的公平性是相对于获取锁的顺序而言的,如果是一个公平锁,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO。 在上面分析的例子来说,只要CAS 设置同步状态成功,则表示当前线程获取了锁,而公平锁则不一样,差异点有两个:

1. FairSync.tryAcquire
       final void lock() {
            acquire(1);
       }

非公平锁在获取锁的时候,会先通过 CAS 进行抢占,而公平锁则不会。

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

这个方法与 nonfairTryAcquire(int acquires)比较,不同的地方在于判断条件多了hasQueuedPredecessors()方法,也就是加入了[同步队列中当前节点是否有前驱节点]的判断,如果该方法返回 true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值