源码分析J.U.C-ReentrantLock

1.ReentrantLock的特点

  • ReentrantLock能够实现可重入,默认使用非公平锁,也支持公平锁。
  • 在JAVA1.6之后性能上ReentrantLock与synchronized相差不大,在使用上ReentrantLock更灵活。
  • ReentrantReadWriteLock相当于是对ReentrantLock的增强,ReentrantReadWriteLock实现了多个读锁共享

2.ReentrantLock用到的知识点

  • CAS
    CAS是一种操作机制能够实现非阻塞同步,它有三个操作数,分别是内存位置V,旧的预期值A、新值B。
    CAS指令执行时,先从内存位置V读取到值,然后和预期值A比较。如果相等,则将此内存位置V的值改为新值 B,返回 true。如果不相等,说明其他线程更新了V值,则不做任何改变,返回 false,整个过程是一个原子操作。
    CAS的原子性是靠CPU的指令集实现的,在java中由sun.misc.Unsafe类中的compareAndSwapInt()、compareAndSwapLong()、compareAndSwapObject()等一些方法实现。

  • volatile
    volatile是java虚拟机提供的最轻量级的同步机制,它有两个特性:

    • 保证变量对所有线程的可见性
    • 禁止指令重排序优化
  • AQS(AbstractQueuedSynchronizer)
    抽象队列同步器,作为一个算法的框架,内部维护了一个状态字段state和一个FIFO的双向队列,并且提供了通过CAS来操作状态和队列的方法。通过“模板方法模式”将获取锁和释放锁延迟到子类中,提供独占和共享两种模式,让子类能够更方便的实现不同的需求。AQS类图如下:
    在这里插入图片描述
    AQS获取锁释放锁框架如下:

	//获取锁
    public final void acquire(int arg) {
	    //尝试获取锁失败并且将当前线程添加到等待队列成功,设置当前线程中断
	    //tryAcquire尝试获取锁方法,延迟到子类实现
        if (!tryAcquire(arg) &&
        	//addWaiter为当前线程在给定模式下创建节点
        	//acquireQueued将当前线程的节点添加到等待队列中
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

	//释放锁
    public final boolean release(int arg) {
    	//尝试释放锁的方法,由子类实现
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//如果后继节点不为空,则唤醒后继节点
            return true;
        }
        return false;
    }

3.ReentrantLock内部结构

ReentrantLock继承Lock接口,通过内部类Sync继承AQS,通过AQS的状态字段实现可重入,然后使用NonfairSync和FairSync类分别继承Sync类并且重写AQS的方法,来实现公平锁和非公平锁。相关类图如下:
在这里插入图片描述
ReentrantLock默认实现为非公平锁,可以通过有参构造方法实现公平锁.如下:

在这里插入图片描述
线程是否获取到到锁通过状态字段state来保证,state通过volatile来保证线程安全,可重入性也是通过state实现,重入次数通过state的值来判断,值为0则无锁.

3.1.非公平锁

非公平锁加锁过程如下:

  1. 先通过CAS争抢锁,抢到锁则直接设置线程独占,加锁成功
  2. 争抢锁失败,再重新尝试获取锁

3.1.1.加锁过程

NonfairSync加锁过程如下:

final void lock() {
    if (compareAndSetState(0, 1))//争抢锁
        setExclusiveOwnerThread(Thread.currentThread());//设置线程独占
    else//争抢失败,尝试重新获取锁
        acquire(1);
}

//AQS中的acquire方法会调用此方法,上面AQS代码中有做介绍
protected final boolean tryAcquire(int acquires) {
	//调用Sync中的非公平方式尝试获取锁
    return nonfairTryAcquire(acquires);
}

Sync尝试获取锁代码如下:

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {//state为0,说明无锁状态,直接使用CAS争抢锁,操作成功则设置线程独占
        if (compareAndSetState(0, acquires)) {
            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;
}

3.1.2.解锁过程

非公平锁与公平锁的解锁过程一样,都是调用ReentrantLock的unlock(),然后调用AQS的release()方法(前面AQS中有展示代码),然后使用Sync的tryRelease()方法,如下:

//尝试解锁
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//减少重入次数
    if (Thread.currentThread() != getExclusiveOwnerThread())//如果当前线程和独占线程不是同一个,则抛出异常
        throw new IllegalMonitorStateException();
    boolean free = false;
    //state不为0,则说明有重入,直接更新state值,返回false
    //state为0,则说明是最后一次解锁,返回true,并且设置独占线程为null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

3.2.公平锁

FairSync为公平锁实现,解锁过程与NonfairSync一样,加锁过程下面介绍

3.2.1.加锁过程

FairSync加锁过程如下:

final void lock() {
	acquire(1);//直接调用AQS的方法获取锁
}

AQS中的acquire方法上面有介绍,AQS会调用FairSync的tryAcquire()方法
FairSync中的tryAcquire()方法如下:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //先判断是否有锁
    if (c == 0) {//如果state为0,即当前无锁的情况
        if (!hasQueuedPredecessors() &&//先判断等待队列是否有线程并且队列的第一个线程是否和当前线程相等,队列不为空并且第一个线程不是当前线程则返回true
            compareAndSetState(0, acquires)) {//如果队列为空,或者队列第一个元素是当前线程,则直接获取锁,并且设置线程独占
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {//如果状态state不为0,且当前线程是独占线程,则当前线程持有锁,增加重入次数
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

3.2.2.解锁过程

解锁过程与NonfairSync一样,上面NonfairSync的解锁过程有介绍.

4.总结

本篇文章对ReentrantLock的公平锁,非公平锁的加锁/解锁过程及可重入性做了分析,对AQS也做了简单介绍.
对于AQS的其他特性,如CLH队列,及队列的自旋没有做详细介绍,还有AQS实现共享锁的CountDownLatch和可重入读写锁ReentrantReadWriteLock以及StampedLock等都没有做分析,后面还会对这些特性及特殊实现做个分析及对比.

发布了61 篇原创文章 · 获赞 85 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/bluuusea/article/details/102557849