ReentrantLock(Lock)锁的底层实现与源码解析

ReentrantLock(Lock)锁的底层实现与源码解析


辅助阅读超链接:AQS抽象类源码解析: AQS抽象类源码解析

1.内置静态内部类及其对象:

经过观察ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer(简称AQS),由于有公平锁和非公平锁需要两个Sync对象,通过继承模板化代码,下面走进这个两个静态内部类:

1.1代表公平锁的静态内部类

   private final Sync sync;
   //代表公平锁的静态内部类
   abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();//用于非公平锁继承重写
		//两个sync对象同时需要该方法,故写在父类
		//nonfairTryAcquire方法将是lock方法间接调用的第一个方法,每次请求锁时都会首先调用该方法。
		//第一次if判断有无锁//第二次判断是否是自己占有锁(偏向)//否则返回false
        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()) {
                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();
        }
		//新建AQS的条件对象
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

		//获取该临界区被哪个线程占有
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
		//获取内部计数器数值
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
		//通过内部计数器数值判断是否有锁
        final boolean isLocked() {
            return getState() != 0;
        }
		//从流重新构造实例(即对其进行反序列化)
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // 初始重置
        }
    }

1.2代表非公平锁的静态内部类

//非公平需要重写两个方法
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
		//通过CAS设置内部计数器为1,即上第一次锁
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

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

2.ReentrantLock的构造方法

两种,即公平、非公平

	public ReentrantLock() {
        sync = new NonfairSync();
    }

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

3.lock加锁过程

Reentrant.lock()方法的调用过程(默认非公平锁):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-od8VJwu0-1633697176490)(D:\javaInstall\Typora\My_All_Pictures\image-20211008191929661.png)]

这些Template模式导致很难直观的看到整个调用过程,其实通过上面调用过程及AbstractQueuedSynchronizer的注释可以发现,AbstractQueuedSynchronizer中抽象了绝大多数Lock的功能,而只把tryAcquire方法延迟到子类中实现。tryAcquire方法的语义在于用具体子类判断请求线程是否可以获得锁,无论成功与否AbstractQueuedSynchronizer都将处理后面的流程。

详见:

Java锁–Lock实现原理(底层实现)_隔壁老王的专栏-CSDN博客_lock的底层实现

4.lock加锁阻塞

LockSupport.park(this)最终把线程交给系统(Linux)内核进行阻塞。当然也不是马上把请求不到锁的线程进行阻塞,还要检查该线程的状态,比如如果该线程处于Cancel状态则没有必要,具体的检查在shouldParkAfterFailedAcquire中。

总体看来,shouldParkAfterFailedAcquire就是靠前继节点判断当前线程是否应该被阻塞,如果前继节点处于CANCELLED状态,则顺便删除这些节点重新构造队列。
至此,锁住线程的逻辑已经完成。

5.释放锁

找出第一个可以unpark的线程,一般说来head.next == head,Head就是第一个线程,但Head.next可能被取消或被置为null,因此比较稳妥的办法是从后往前找第一个可用线程。貌似回溯会导致性能降低,其实这个发生的几率很小,所以不会有性能影响。之后便是通知系统内核继续该线程,在Linux下是通过pthread_mutex_unlock完成。之后,被解锁的线程进入上面所说的重新竞争状态。

6.Lock 和 Synchronized区别

AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列(Craig, Landin, and Hagersten (CLH))容纳所有的阻塞线程,而对该队列的操作均通过**Lock-Free(CAS)**操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。

synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList( 处于wait状态的线程,会被加入到_WaitSet)和EntryList(处于等待锁block状态的线程,会被加入到该列表),目的是为了降低线程的出列速度;当然也实现了偏向锁,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。

当然Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。

参考资料

ReentrantLock核心源码分析_weixin_41590779的博客-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值