ReentrantLock原理

news/2024/5/20 10:05:20 标签: juc, java

实现了Lock接口
内部也维护了一个同步器Sync继承自AQS,Sync是抽象的,两个实现NonFairSync和FairSync

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

非公平锁加锁解锁流程

java">// 加锁
CAS修改State状态0->1
修改成功,则将Owner线程设置为当前线程
修改失败
调用AQS中的acquire方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&  // 再次尝试获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
获取失败,addWaiter将当前线程封装为一个Node对象,加入到等待的双向Node链表中
进入acquireQueued逻辑
acquireQueued会在一个死循环中不断尝试获得锁,失败后park阻塞
1.如果是紧邻着head(head指向的Node节点的下一个Node节点),那么可以再次tryAcquire尝试获取锁,当然这时state仍为1,失败
2.进入shouldParkAfterFailedAcquire逻辑,将前驱node,即head的waitStatus改为-1,这次返回false
3.当再次进入shouldParkAfterFailedAcquire时,这时因为其前驱node的waitStatus已经是-1,这次返回true
4.进入parkAndCheckInterrupt,阻塞

在这里插入图片描述
在这里插入图片描述

java">// 解锁
调用了AQS的release方法
public final boolean release(int arg) {
	// tryRelease中将OwnerThread设置为null,将state设置为0
    if (tryRelease(arg)) { // 调用ReentrantLock中Sync内部类的tryRelease方法
        Node h = head;	// 看head的指向是否为null以及waitstatus
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 找到等待链表中离head最近的一个Node(没取消的),unpark恢复其运行
        return true;
    }
    return false;
}
// 线程被唤醒后
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
	for (;;) {
	    final Node p = node.predecessor();
	    if (p == head && tryAcquire(arg)) { // 唤醒后恢复运行拿到锁
	        setHead(node); // 将head指向为自己的Node节点,自己的Node节点也不再关联线程
	        p.next = null; // help GC,将之前head指向的Node节点断开
	        return interrupted;
	    }
	    if (shouldParkAfterFailedAcquire(p, node))
	        interrupted |= parkAndCheckInterrupt(); // 获取锁时,是在这里被park阻塞的,所以也从这里恢复运行
	}
}

在这里插入图片描述
非公平锁,当释放锁的时候,又来了一个线程,会和刚被唤醒的线程竞争
被唤醒的线程没竞争过,则又会park,去睡觉

可重入原理

获取锁时,如果state已经为1,会判断当前线程是不是Owner线程
如果是,表示发生了锁重入,会让state++
释放锁时调用tryRelease方法,会让state减1,减完之后发现不是0,则返回false,不会释放锁
只有当state减完之后变为0了,才会释放锁,返回true

可打断原理

默认是不可打断的,因为被打断后恢复运行,会继续尝试获得锁,获得到会将打断标记true作为结果返回,获得不到会继续阻塞

java">final boolean acquireQueued(final Node node, int arg) {
	boolean interrupted = false;
	for (;;) {
		final Node p = node.predecessor();
		if (p == head && tryAcquire(arg)) {
			setHead(node);
			p.next = null; // help GC
			return interrupted; // 获取锁时,会将打断标记作为结果返回
		}
		if (shouldParkAfterFailedAcquire(p, node))
			// 被打断唤醒后会将打断标记清除(为了下面获取锁失败时还可以park住),将true赋给interrupted
			interrupted |= parkAndCheckInterrupt();
	}
}
// 这里acquireQueued返回true,表示是被打断后恢复运行拿到了锁
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 这里会重新设置打断标记,因为之前清理了打断标记
        selfInterrupt();
}

可打断(使用另一个获取锁的方法),打断唤醒后,会直接抛出异常,不会再尝试获取锁

公平锁原理

java">protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
    	// 竞争锁之前先判断等待链表中的情况
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 判断当前线程是不是Owner线程
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
    Node h, s;
    if ((h = head) != null) { // head是否为null
        if ((s = h.next) == null || s.waitStatus > 0) {
            s = null; // traverse in case of concurrent cancellation
            for (Node p = tail; p != h && p != null; p = p.prev) {
                if (p.waitStatus <= 0)
                    s = p;
            }
        }
        // 如果有head指向的Node节点的下一个Node节点,且关联的线程不是自己
        // 则自己就不要去竞争锁了
        if (s != null && s.thread != Thread.currentThread())
            return true;
    }
    return false;
}

条件变量实现原理

每个条件变量对应着一个等待链表,实现类是ConditionObject,维护了一个双向链表
await流程

java">public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();// 创建Node对象,添加到等待链表中.Adds a new waiter to wait queue.
    long savedState = fullyRelease(node);// 释放锁,唤醒AQS等待链表中的一个Node节点
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this); // 阻塞住
    }
}

在这里插入图片描述
在这里插入图片描述
signal流程
先检查当前线程是不是锁的持有者,不是则抛出异常
将ConditionObject维护的等待链表中的第一个Node节点移除,转移到AQS的等待链表中


http://www.niftyadmin.cn/n/241766.html

相关文章

[Uboot] 添加自己配置文件defconfig以及头文件

一:添加配置文件 1.拷贝 /uboot/configs下自己单板的配置文件 拷贝并且重命名 cp mx6ull_14x14_evk_emmc_defconfig my_imx_defconfig 2.配置configs,很多情况下我们都是默认写法,然后是为了单纯有自己的配置文件,但是这里不同启动介质,以及修改配置头文件 CONFIG_SYS_EXTRA_OP…

http(1)

主要介绍http 1.0 我们在浏览器中输入一个网址&#xff0c;稍等片刻就看见了网页 客户端会发送一个http请求&#xff0c;要求返回cn.bing.com这个网址&#xff0c;服务器收到请求后就会返回一个html页面 &#xff08;服务器根据请求找到客户端想要的资源&#xff0c;然后把这个…

浏览器输入 http 自动转 https 问题解决方法

目录 表象 原因 解决方案 解决方案一 解决方案二 表象 今天在开发的过程中遇到一个问题&#xff0c;我们项目的地址是 “http://xxx.xxx.com/website/” &#xff0c;结果粘贴到浏览器里自动跳转成了 “https://xxx.xxx.com/website/”。百思不解啊&#xff0c;为啥呢。 …

c++学习(day1)

文章目录 一. 课前绪论1.1 面向对象概念1.2 C对C的兼容1.3 C的基本特点 二、第一个C程序2.1 hello word2.2 输出流对象cout2.3 输入流对象cin2.4 cout格式化输出 三、命名空间3.1 C中的名字3.2 命名空间的作用3.3 std命名空间的使用方式3.4 定义自己的命名空间3.5 多个命名空间…

检测并打印C++编译器支持的feature(附Visual Studio 2022测试结果)

C标准快速迭代&#xff0c;不同的系统平台和编译器对C各种新功能的支持不同&#xff0c;通过这个程序可以测试所用编译器对各个版本C的支持情况。另一方面&#xff0c;可以在代码中通过这些宏针对不同版本编写不同的代码分支。 源码下面附上Visual Studio 2022的测试结果&#…

CFS调度器

转自&#xff1a;https://www.cnblogs.com/yungyu16/p/13050653.html?utm_sourcetuicool 导语 CFS&#xff08;完全公平调度器&#xff09;是Linux内核2.6.23版本开始采用的进程调度器&#xff0c;它的基本原理是这样的&#xff1a;设定一个调度周期&#xff08;sched_laten…

瑞吉外卖项目——缓存优化

用户数量多&#xff0c;系统访问量大 频繁访问数据库&#xff0c;系统性能下降&#xff0c;用户体验差 环境搭建 maven坐标 在项目的pom.xml文件中导入spring data redis的maven坐标: <dependency><groupId>org.springframework.boot</groupId><arti…

【大数据之Hadoop】十六、MapReduce之Join

1 Reduce Join Map端&#xff1a; 为来自不同表或文件的key/value对&#xff0c;打标签以区别不同来源的记录。然后用连接字段作为key&#xff0c;其余部分和新加的标志作为value&#xff0c;最后进行输出。 Reduce端&#xff1a; 在每一个分组当中将那些来源于不同文件的记录…