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)


目录
相关文章
|
22天前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
10天前
|
存储 SQL 监控
JAVA 线程池的分析和使用
JAVA 线程池的分析和使用
14 0
|
3天前
|
安全 Java Python
GIL是Python解释器的锁,确保单个进程中字节码执行的串行化,以保护内存管理,但限制了多线程并行性。
【6月更文挑战第20天】GIL是Python解释器的锁,确保单个进程中字节码执行的串行化,以保护内存管理,但限制了多线程并行性。线程池通过预创建线程池来管理资源,减少线程创建销毁开销,提高效率。示例展示了如何使用Python实现一个简单的线程池,用于执行多个耗时任务。
15 6
|
2天前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
8 2
|
5天前
|
API
linux---线程互斥锁总结及代码实现
linux---线程互斥锁总结及代码实现
|
3天前
|
调度
线程操作:锁、条件变量的使用
线程操作:锁、条件变量的使用
11 1
|
5天前
|
API
Linux---线程读写锁详解及代码实现
Linux---线程读写锁详解及代码实现
|
8天前
|
Java Python
Python中的并发编程(3)线程池、锁
Python中的并发编程(3)线程池、锁
|
16天前
|
Python
Python多线程中递归锁如何解决死锁问题的详细阐述
Python多线程中递归锁如何解决死锁问题的详细阐述
|
16天前
|
安全 Python
Python多线程中的死锁与递归锁
Python多线程中的死锁与递归锁

热门文章

最新文章