ReentrantLock源码分析笔记-单线程公平锁为例

简介: ReentrantLock源码分析笔记-单线程公平锁为例

前提


1)大致了解AQS原理(☆☆☆)


2)熟悉双向链表的基本操作


3)本文以公平锁上锁释放锁为例跟ReentrantLock代码(☆☆☆)


4)本文以单线程抢占锁释放锁为例(☆☆☆)


5)建议了解公平锁和非公平锁的区别


6)较好的理解能力(作者表达能力差)


AQS


AQS的核心思想(参考版)


如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

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

AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。


用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。


**注意:AQS是自旋锁:**在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功


实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物


AQS的核心思想(作者版)


大致的思想是:


当一个线程发出上锁的请求时,会看state状态,


如果是0(没有上锁),则上锁(state=1),处理业务逻辑;


如果不是0(别的线程占有锁),则把当前线程封装成一个Node节点插入双向链表中等待。


当上锁的线程释放锁后会唤醒双向链表中的第一个节点中的线程继续使用锁。


当然中间有很多细节,需要自己悟仔细悟,我只想说  Doug Lea NB.


public abstract class AbstractQueuedSynchronizer
        extends AbstractOwnableSynchronizer
        implements java.io.Serializable {
    /*当前持有锁的线程,AbstractOwnableSynchronizer里的属性*/
    private transient Thread exclusiveOwnerThread;
    /*双向链表的头指针*/
    private transient volatile Node head;
    /*双向链表的尾指针*/
    private transient volatile Node tail;
    /*锁的状态,1表示上锁,0表示没有锁,大于1表示可重入锁*/
    private volatile int state;
    /*双向链表的Node节点*/
    static final class Node {
        /*每一个请求上锁的线程都会封装成一个双向链表*/
        volatile Thread thread;
        /*双向链表的prev指针*/
        volatile Node prev;
        /*双向链表的next指针*/
        volatile Node next;
    }
}


测试代码:单线程公平锁为例


图解


17.png


上锁


main函数


//一个线程上锁释放锁,没有任何并发情况
public class Start {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(true);
        System.out.println("--lock-");
        lock.lock();
        System.out.println("---logic--");
        lock.unlock();
        System.out.println("--unlock--");
    }
}


跟lock.lock()代码(公平锁),就跟到下面代码


//FairSync 
final void lock() {
            //参数1很重要
            acquire(1);
}


继续跟


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


下面开始执行 tryAcquire(arg) 方法


//FairSync
protected final boolean tryAcquire(int acquires) {//acquires=1
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取lock的状态(0表示没有上锁,1表示上锁,大于1表示重入锁)
            int c = getState();//c=0,因为c是int类型,初始化为0
            if (c == 0) {//走这个分支
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
               //不走这个分支
               //不走这个分支
               //不走这个分支
            }
            return false;
        }


跟hasQueuedPredecessors()方法(这个方法贼难,贼难,贼难)


//AbstractQueuedSynchronizer
public final boolean hasQueuedPredecessors() {
        //获取双向链表的头结点和尾结点,此时都为null
        Node t = tail; 
        Node h = head;
        Node s;
        //h != t  返回 false,后面就不用执行了,整体返回false
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }


tryAcquire(arg)里的hasQueuedPredecessors()返回false,    !hasQueuedPredecessors() 返回true, 所以继续跟compareAndSetState(0, acquires)方法


//AbstractQueuedSynchronizer
protected final boolean compareAndSetState(int expect, int update) { //expect=0,update=1
        //stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        //修改stateOffset就是修改state
        // 本文的逻辑是在单线程情况下,所以cas会成功操作
        //即 return  true
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }


此时tryAcquire方法里!hasQueuedPredecessors() &&compareAndSetState(0, acquires)  整体是真,将执行setExclusiveOwnerThread(current)方法


//AbstractOwnableSynchronizer
protected final void setExclusiveOwnerThread(Thread thread) {
        //上面通过cas成功设置锁的状态,然后这里将使用锁的线程保存下来
        exclusiveOwnerThread = thread;
    }


acquire里的tryAcquire返回true,   !tryAcquire  返回false, 即acquire执行完毕


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


------------------------------------------------>此时就是上锁成功  (最显著的结果就是将state从0变为1,并且在一个变量AbstractOwnableSynchronizer中的exclusiveOwnerThread属性保存了拥有锁的线程)


释放锁


lock.unlock()跟到下面代码


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


继续跟进release方法


//AbstractQueuedSynchronizer     
public final boolean release(int arg) {//GAG=1
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


跟tryRelease方法


//Sync
protected final boolean tryRelease(int releases) {
            //将state减少releases,此处state=1,releases=1
            int c = getState() - releases;
            //如果释放锁的线程不是当前线程,肯定不合适,谁上锁谁释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果c==0,说明锁释的状态为空闲,将当前上锁的线程设置为空
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //修改新的state状态
            setState(c);
            return free;
        }


------------------------------------------------>此时就是释放锁成功 (最显著的结果就是将state从1变为0,并且在一个变量AbstractOwnableSynchronizer中的exclusiveOwnerThread属性设置为null)


目录
相关文章
|
2月前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
49 1
|
3月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
47 2
|
2月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
68 0
|
3月前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
2月前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
41 1
|
2月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
41 0
|
2月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
30 0
|
2月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
36 0
|
2月前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
46 0
|
2月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解