首页 > 其他分享 >锁-基础篇(2)

锁-基础篇(2)

时间:2023-12-08 20:46:46浏览次数:25  
标签:重入 ReentrantLock 基础 读锁 互斥 state 线程

ReentrantLock

  1. 可重入 ReentrantLock 和 syncronized 关键字一样,都是可重入锁,不过两者实现原理稍有差别, RetrantLock 利用 AQS 的的 state 状态来判断资源是否已锁,同一线程重入加锁, state 的状态 +1 ; 同一线程重入解锁, state 状态 -1 (解锁必须为当前独占线程,否则异常); 当 state 为 0 时解锁成功。
  2. 需要手动加锁、解锁synchronized 关键字是自动进行加锁、解锁的,而 ReentrantLock 需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成,来手动加锁、解锁。
  3. 支持设置锁的超时时间 synchronized 关键字无法设置锁的超时时间,如果一个获得锁的线程内部发生死锁, 那么其他线程就会一直进入阻塞状态,而 ReentrantLock 提供 tryLock 方法,允许设置线程获取锁的超时时间, 如果超时,则跳过,不进行任何操作,避免死锁的发生。
  4. 支持公平/非公平锁synchronized 关键字是一种非公平锁,先抢到锁的线程先执行。 而 ReentrantLock 的构造方法中允许设置 true/false 来实现公平、非公平锁,如果设置为 true , 则线程获取锁要遵循"先来后到"的规则,每次都会构造一个线程 Node ,然后到双向链表的"尾巴"后面排队, 等待前面的 Node 释放锁资源。
  5. 可中断锁 ReentrantLock 中的 lockInterruptibly() 方法使得线程可以在被阻塞时响应中断, 比如一个线程 t1 通过 lockInterruptibly() 方法获取到一个可重入锁,并执行一个长时间的任务, 另一个线程通过 interrupt() 方法就可以立刻打断 t1 线程的执行,来获取t1持有的那个可重入锁。 而通过 ReentrantLock 的 lock() 方法或者 Synchronized 持有锁的线程是不会响应 其他线程的 interrupt() 方法的,直到该方法主动释放锁之后才会响应 interrupt() 方法。

ReentrantReadWriteLock

ReentrantReadWriteLock (读写锁)其实是两把锁,一把是 WriteLock (写锁),一把是读锁, ReadLock 。

读写锁的规则是:

读读不互斥、读写互斥、写写互斥。在一些实际的场景中,读操作的频率远远高于写操作,如果直接用一般的锁进行并发控制的话,就会读读互斥、读写互斥、写写互斥,效率低下,读写锁的产生就是为了优化这种场景的操作效率。一般情况下独占锁的效率低来源于高并发下对临界区的激烈竞争导致线程上下文切换。因此当并发不是很高的情况下,读写锁由于需要额外维护读锁的状态,可能还不如独占锁的效率高,因此需要根据实际情况选择使用。

ReentrantReadWriteLock 的原理也是基于 AQS 进行实现的,与 ReentrantLock 的差别在于:

ReentrantReadWriteLock 锁拥有共享锁、排他锁属性。读写锁中的加锁、释放锁也是基于 Sync (继承于 AQS ),并且主要使用 AQS 中的 state 和 node 中的 waitState 变量进行实现的。实现读写锁与实现普通互斥锁的主要区别在于需要分别记录读锁状态及写锁状态,并且等待队列中需要区别处理两种加锁操作。 ReentrantReadWriteLock 中将AQS 中的 int 类型的 state 分为高 16 位与第 16 位分别记录读锁和写锁的状态。

WriteLock(写锁)是悲观锁(排他锁、互斥锁)

通过计算 state&((1<<16)-1) ,将 state 的高 16 位全部抹去,因此 state 的低位记录着写锁的重入计数。

ReadLock(读锁)是共享锁(乐观锁)

通过计算 state>>>16 进行无符号补 0 ,右移 16 位,因此 state 的高位记录着写锁的重入计数.读锁获取锁的过程比写锁稍微复杂些,首先判断写锁是否为 0 并且当前线程不占有独占锁,直接返回;
否则,判断读线程是否需要被阻塞并且读锁数量是否小于最大值并且比较设置状态成功,若当前没有读锁,则设置第一个读线程 firstReader 和 firstReaderHoldCount ;若当前线程线程为第一个读线程,
则增加 firstReaderHoldCount ;否则,将设置当前线程对应的 HoldCounter 对象的值,更新成功后会在 firstReaderHoldCount 中 readHolds ( ThreadLocal 类型的)的本线程副本中记录当前线程重入数,
这是为了实现 JDK1.6 中加入的 getReadHoldCount ()方法的,这个方法能获取当前线程重入共享锁的次数( state 中记录的是多个线程的总重入次数),加入了这个方法让代码复杂了不少,但是其原理还是很简单的:如果当前只有一个线程的话,还不需要动用 ThreadLocal ,直接往 firstReaderHoldCount 这个成员变量里存重入数,当有第二个线程来的时候,就要动用 ThreadLocal 变量 readHolds 了,每个线程拥有自己的副本,用来保存自己的重入数。

LongAdder

  1. LongAdder在高并发的情况下,我们对一个 Integer 类型的整数直接进行 i++ 的时候,无法保证操作的原子性,会出现线程安全的问题。为此我们会用 juc 下的 AtomicInteger ,它是一个提供原子操作的 Interger 类,内部也是通过 CAS 实现线程安全的。但当大量线程同时去访问时,就会因为大量线程执行 CAS 操作失败而进行空旋转,导致 CPU 资源消耗过多,而且执行效率也不高。 Doug Lea 大神应该也不满意,于是在 JDK1.8 中对 CAS 进行了优化,提供了 LongAdder ,它是基于了 CAS 分段锁的思想实现的。
  2. LongAdder 也是基于 Unsafe 提供的 CAS 操作 +valitale 去实现的。在 LongAdder 的父类 Striped64 中维护着一个 base 变量和一个 cell 数组,当多个线程操作一个变量的时候,先会在这个 base 变量上进行 cas 操作,当它发现线程增多的时候,就会使用 cell 数组。比如当 base 将要更新的时候发现线程增多(也就是调用 casBase 方法更新 base 值失败),那么它会自动使用 cell 数组,每一个线程对应于一个 cell ,在每一个线程中对该 cell 进行 cas 操作,这样就可以将单一 value 的更新压力分担到个 value 中去,降低单个 value 的 “热度”,同时也减少了大量线程的空转,提高并发效率,分散并发压力。这种分段锁需要额外维护一个内存空间 cells ,不过在高并发场景下,这点成本几乎可以忽略。分段锁是一种优秀的优化思想, juc 中提供的的 ConcurrentHashMap 也是基于分段锁保证读写操作的线程安全。

标签:重入,ReentrantLock,基础,读锁,互斥,state,线程
From: https://www.cnblogs.com/nxjblog/p/17888986.html

相关文章

  • 二分——acwing算法基础课笔记
    个人笔记,欢迎补充、指正。此次完全以个人理解来写。整数二分 整数二分有两种,分别是找左边界和找右边界。 寻找符合要求的左边界:绿色点intbsearch_1(intl,intr){while(l<r){intmid=l+r>>1;//对应下界,最左if(check(mid))r=......
  • 一文读懂生成式人工智能的所有基础知识(上)
    生成式人工智能已经成为一项突破性技术,改变了我们的生活与工作方式。它不仅是一种技术现象,更是一种广泛应用于实际生活的工具。2023年,世界见证了生成式人工智能的多项突破,其中最引人注目的当属由OpenAI开发的最新版本ChatGPT。该工具于2022年11月向公众发布测试,短短五天内就......
  • day17 模块基础
    day17开始2023年12月8日周五14:16:52time模块:importtime时间戳:time.time()从1970年开始过了多少秒格式化时间:time.strftime("%Y%m%d")结构化时间:time.localtime()睡眠:time.sleepsplit()对字符串进行切割切割的结果以列表进行保存datetime模块:datetime.datetime......
  • 13、QT窗口API函数基础知识
    QT窗口API函数geometry()用于获取窗口在屏幕上的几何位置和大小QRectgeo=widget->geometry();intx=geo.x();//窗口左上角的x坐标inty=geo.y();//窗口左上角的y坐标intw=geo.width();//窗口的宽度inth=geo.height();//窗口的高度width()函数返......
  • C基础常用代码
    1.写文件#include<stdio.h>#include<unistd.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<errno.h>#include<string.h>//writeASCIIvalueintfile_write(char*fn,intval){in......
  • 第五章:内容安全基础
    ##教材学习总结思维导图:总揽全局各个小节思维导图及简介第一节:信息内容安全概述*数据内容成为互联网的中心关注点*正面影响:大数据正在逐步演变为生产力*负面影响:不良信息的大量传播,不正当行为泛滥*产生原因:(1)在互联网爆炸性发展的同时,相关方面的规范和管理措施未能同步发......
  • 静态HTTP的基础知识:菜鸟的教程与指南
    大家好,今天我要给大家讲解一个非常基础但重要的知识点——静态HTTP。如果你是一位初入互联网的小白,对于HTTP这个缩写可能还有些陌生。没关系,今天我们就来揭开它的神秘面纱。首先,让我们想象一下,当你在浏览器中输入一个网址,比如www.example.com,你的浏览器就开始和这个网站的服务器进......
  • Java语言基础知识全总结
    一.Java的优点1.      跨平台性。一次编译,到处运行。Java编译器会将Java代码编译成能在JVM上直接运行的字节码文件,C++会将源代码编译成可执行的二进制代码文件,所以C++执行速度快2.      纯面向对象。Java所有的代码都必须在类中书写。C++兼具面向对象和面向过程的特......
  • 桥牌基础叫牌
    一般常识点力(P)A4K3Q2J1;大牌:AKQ(输墩计算:一门花色少一张大牌多一个输墩控制:AK成局:高花4阶,NT3阶,低花5阶开叫第一门花色要求5张以上,开叫第二门花色要求4张以上一般准则将牌选择:高花优先原则>NT次优先>低花无奈选择我方联手23点以上可以成局,25点以上逼叫到局......
  • 【scikit-learn基础】--『数据加载』之样本生成器
    除了内置的数据集,scikit-learn还提供了随机样本的生成器。通过这些生成器函数,可以生成具有特定特性和分布的随机数据集,以帮助进行机器学习算法的研究、测试和比较。目前,scikit-learn库(v1.3.0版)中有20个不同的生成样本的函数。本篇重点介绍其中几个具有代表性的函数。1.分类聚类......