sheng的学习笔记-ReentrantLock和AQS原理和源码解析

目录

ReentrantLock基础

ReentrantLock 和 Synchronized的对比

关系图

AbstractQueuedSynchronizer

AQS中有几个重要的概念:

ReentrantLock

ReentrantLock内部结构

 lock

​编辑

 Sync

sync源码

非公平锁

公平锁

加锁:

解锁:

参考文章


ReentrantLock基础

ReentrantLock的功能是实现代码段的并发访问控制,也就是通常意义上所说的锁,java中实现锁有两种方式,一种是本文所提的ReentrantLock,另一种是synchronized

ReentrantLock可重入的互斥锁,底层基于AbstractQueuedSynchronizer实现

可重入性是指当一个线程拥有一个方法的锁以后,是否还可以进入该方法,一般这种情况出现在递归中。ReentrantLock的可重入性是基于Thread.currentThread()实现的,是线程粒度的,也就是说当前线程获得一个锁以后,当前线程的所有方法都可以获得这个锁。

Reentrant支持公平锁和非公平锁

非公平锁的效率高于公平锁

非公平锁可能出现 线程饥饿问题——部分线程迟迟无法获得资源

ReentrantLock 和 Synchronized的对比

关系图

ReentrantLock实现了Lock接口,Lock接口是Java中对锁操作行为的统一规范

AbstractQueuedSynchronizer

AbstractQueuedSynchronizer抽象类定义了一套多线程访问共享资源的同步模板,解决了实现同步器时涉及的大量细节问题,能够极大地减少实现工作,用大白话来说,AbstractQueuedSynchronizer为加锁和解锁过程提供了统一的模板函数,只有少量细节由子类自己决定

AbstractQueuedSynchronizer会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态。

  AQS是一个同步器,设计模式是模板模式。核心数据结构:双向链表 + state(锁状态);底层操作:CAS

AQS中有几个重要的概念:

  • state:用来记录可重入锁的上锁次数;

  • exclusiveOwnerThread:AQS继承了AbstractOwnableSynchronizer,而其中有个属性exclusiveOwnerThread,用来记录当前独占锁的线程是谁;

  • CLH同步队列:FIFO双向链表队列,此CLH队列是原CLH的变种,由原来的不断自旋改为了阻塞机制。队列中有头节点和尾节点两个指针,尾节点就是指向最后一个节点,而头节点为了便于判断,永远指向一个空节点,之后才是第一个有数据的节点;

  • 条件队列:能够使某些线程一起等待某个条件具备时,才会被唤醒,唤醒后会被放到CLH队列中重新争夺锁资源。

    AQS定义资源的访问方式有两种:

  • 独占模式:只有一个线程能够获取锁,如ReentrantLock;

  • 共享模式:多个线程可以同时获取到锁,如Semaphore、CountDownLatch和CyclicBarrier。

ReentrantLock

ReentrantLock实现了Lock接口,lock定义如下

ReentrantLock内部结构

ReentrantLock要去实现这些函数,遵循着解耦可扩展设计,ReentrantLock内部定义了专门的组件Sync, Sync继承AbstractQueuedSynchronizer提供释放资源的实现,NonfairSyncFairSync是基于Sync扩展的子类,即ReentrantLock的非公平模式与公平模式 

 

ReentrantLock中,它对AbstractQueuedSynchronizerstate状态值定义为线程获取该锁的重入次数,state状态值为0表示当前没有被任何线程持有,state状态值为1表示被其他线程持有,因为支持可重入,如果是持有锁的线程,再次获取同一把锁,直接成功,并且state状态值+1,线程释放锁state状态值-1,同理重入多次锁的线程,需要释放相应的次数。

 lock

ReentrantLock的lock是用的sync实现的,sync依赖AQS实现,AQS使用模板模式,一些函数让子类实现(公平锁和非公平锁)

sync的lock代码 

其中initialTryLock会调用公平锁/非公平锁的方法,acquire调用AQS的方法

 Sync

Sync继承了AbstractQueuedSynchronizer,可以说是ReentrantLock的核心,后面的NonfairSyncFairSync都是基于Sync扩展出来的子类。

sync源码

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs non-fair tryLock.
         */
        @ReservedStackAccess
        final boolean tryLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, 1)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (getExclusiveOwnerThread() == current) {
                if (++c < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(c);
                return true;
            }
            return false;
        }

        /**
         * Checks for reentrancy and acquires if lock immediately
         * available under fair vs nonfair rules. Locking methods
         * perform initialTryLock check before relaying to
         * corresponding AQS acquire methods.
         */
        abstract boolean initialTryLock();

        @ReservedStackAccess
        final void lock() {
            if (!initialTryLock())
                acquire(1);
        }

        @ReservedStackAccess
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (getExclusiveOwnerThread() != Thread.currentThread())
                throw new IllegalMonitorStateException();
            boolean free = (c == 0);
            if (free)
                setExclusiveOwnerThread(null);
            setState(c);
            return free;
        }
        
    }

非公平锁

NonfairSync 类继承了 Sync类,表示采用非公平策略获取锁,其实现了 Sync类中抽象的 lock方法,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。Acquire方法是 FairSync和 UnfairSync的父类 AQS中的核心方法。


FairSync 类也继承了 Sync类,表示采用公平策略获取锁,其实现了 Sync类中的抽象 lock方法,源码如下

公平锁

FairSync流程与NonfairSync基本一致,唯一的区别就是在C A S执行前,多了一步hasQueuedPredecessors函数,这一步就是判断当前线程是不是CLH队列被唤醒的线程,如果是就执行C A S,否则获取资源失败

当资源空闲时,它总是会先判断 sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。

只要资源被其他线程占用,该线程就会添加到 sync queue中的尾部,而不会先尝试获取资源。这也是和 Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

加锁:

  • 通过ReentrantLock的加锁方法Lock进行加锁操作。
  • 会调用到内部类Sync的Lock方法,由于Sync#lock是抽象方法,根据ReentrantLock初始化选择的公平锁和非公平锁,执行相关内部类的Lock方法,本质上都会执行AQS的Acquire方法。
  • AQS的Acquire方法会执行tryAcquire方法,但是由于tryAcquire需要自定义同步器实现,因此执行了ReentrantLock中的tryAcquire方法,由于ReentrantLock是通过公平锁和非公平锁内部类实现的tryAcquire方法,因此会根据锁类型不同,执行不同的tryAcquire。
  • tryAcquire是获取锁逻辑,获取失败后,会执行框架 AQS的后续逻辑,跟ReentrantLock自定义同步器无关。

解锁:

  • 通过 ReentrantLock的解锁方法 Unlock进行解锁。
  • Unlock会调用内部类 Sync的 Release方法,该方法继承于AQS。
  • Release中会调用 tryRelease方法,tryRelease需要自定义同步器实现,tryRelease只在ReentrantLock中的Sync实现,因此可以看出,释放锁的过程,并不区分是否为公平锁。
  • 释放成功后,所有处理由AQS框架完成,与自定义同步器无关。

通过上面的描述,大概可以总结出 ReentrantLock加锁解锁时 API层核心方法的映射关系。

参考文章

百度安全验证

Java并发——通过ReentrantLock与Semaphore揭秘AQS独占模式与共享模式 - 知乎

ReentrantLock 锁详解_程序猿进阶的博客-CSDN博客_reentrantlock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值