Sychronized Volatile Atomic ReentrantLock工具类 AQS底层实现

本文主要是介绍Sychronized Volatile Atomic ReentrantLock工具类 AQS底层实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、Synchronized

Synchronized保障的有序性是指多个线程依次执行同步代码块或者同步方法,对于代码块中的多条指令无法保障有序性;

Sychronized的对象在new时要加上final(final Object o = new Object; sychronized(o){}),防止该对象被有些线程篡改,因为锁是加在对象头上的,如果o被指向了新的一个对象,那么其他线程就可以重新在该新的对象头上加锁,导致多线程同时访问某个程序块或者方法。

Sychronized重量级的体现:锁是由操作系统管理的,所以一个用户线程想要获得锁就得向操作系统申请,这里就涉及到了内核态与用户态之间的资源交互,比较耗费CPU资源;锁升级就是在成为重量级锁之前可以直接在用户态获得锁(不加锁,只是改变对象头的标志位),不需要通过kernel申请锁。

在这里插入图片描述

  • 锁升级:除了重量级锁其他锁都没有真正加上锁;
  • 重量级Synchronized锁其实和ReentrantLock差不多,底层有有一个waitSet存放想要获取锁的线程,通过OS调度唤醒即将获取锁的线程;

请添加图片描述

二、Volatile

只能用来修饰简单对象,不能用来修饰引用对象(volatile List<>() list),因为他无法监控引用对象所指向的对象的改变情况,也就是不能保证对象的引用对象的可见性;

  • 可见性:(Volatile底层通过总线机制 + 缓存一致性协议实现可见性)

如果不加volatile,CPU之间缓存行改变某一个变量不能控制其实时写入共享内存,也不能控制其他CPU实时去内存中读取该变量的最新值,也就是CPU之间不可见;

  • 有序性:(底层是由CPU的原语loadfence和storefence实现的)

DCL(单例懒汉式的double check lock)问题必须加两次if(Instance != null)判断(防止多线程同时进入第一个if中,第二个线程获得锁后重新new一个Instance对象),而且类的字段必须是加Volatile关键字(防止对象乱序半初始化问题,导致其他线程得到未初始化完的对象);

public class SingletonDCL {/** DCL懒汉模式对象的属性必须使用volatile修饰,防止多线程使用半初始化的对象中的java默认属性值 */private static volatile SingletonDCL Instance;private  SingletonDCL(){}/*** Volatile避免多线程获取半初始化对象的java默认属性值* DCL,解决多线程造成的多例现象* @return*/public static SingletonDCL getInstance(){/** 第一重检查,当已经有对象建立了之后,直接复用当前对象 */if(Instance == null){synchronized (SingletonDCL.class){/** 第二重检查,避免两个线程同时进入上述if语句块,当一个线程释放锁后,第二个线程获得锁,如果不加该判断会new一个新的对象 */if(Instance == null){try{Thread.sleep(1);}catch (Exception e){e.printStackTrace();}Instance = new SingletonDCL();}}}/** 当上一个线程创建了一个半初始化对象时,第二个线程判断已有了Instance直接到这里获取这个半初始化对象,后续该线程使用的就是这个半初始化对象的默认属性值了 */return Instance;}
}

三、Atomic原子类

底层都是基于CAS实现的;(CAS是CPU原语级别的指令支持,CAS比较数据的环节虽然是多个步骤,但是被CPU保证是原子性的)

Atomic.getAndIncrement(); // 安全的自增一

四、ReentrantLock以及各工具类

  • note1:Lock.wait()释放锁,但是Lock.notify() 不释放锁,所以如果wait的线程需要的锁是执行notify方法的线程持有的锁,那么wiat的线程无法获取锁,无法执行,所以执行notify的线程必须再执行一次wait让出锁,原先被阻塞的线程才能继续执行;— 是否能唤醒其他线程的问题

  • note2:唤醒者有时需要在唤醒其他线程后,暂停自身线程,等待其他线程唤醒它;线程间可以通信,但是如果要求一个线程唤醒另一个线程时立马得到另一个线程的输出(有输出顺序的要求),此时第一个线程唤醒对方线程后要让自己先停一下,等被唤醒的线程执行完后再来唤醒其本身。— 是否能保证唤醒线程与被唤醒线程的有序输出问题

1) t1 唤醒t2:unpark(t2) — t1自身停止:park() / t2执行 —t2唤醒t1:unpark(t1);
2)CountDownLatch同理,一个线程await被解开后是和另一个线程同时执行的,不能保证被解开await的线程立马可以输出,很大可能是另一个线程继续执行,提前输出,所以唤醒者自身要await等待被唤醒者执行完毕后再把它唤醒;

  • note3:wait方法必须在while体里面,不能用if代替while,因为if体中线程被唤醒后不会再执行判断语句,此时可能在线程唤醒期间判断条件由满足又变成了不满足,然而线程却执行了后续的逻辑操作,而while体中,线程被唤醒后会再次判断是否满足执行的条件,保证可以执行后续的逻辑操作。

多种工具类:

1) CountDownLatch

一个线程等多个线程执行完countDown(),该线程再继续执行;

public class CountDownLatchDemo {ReentrantLock lock = new ReentrantLock();CountDownLatch countDownLatch = new CountDownLatch(10);int count = 0;public void add(){lock.lock();try{count ++;countDownLatch.countDown();}finally {lock.unlock();}}public static void main(String[] args) {Thread[] threads = new Thread[10];CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();for(int i=0; i<10; i++){threads[i] = new Thread(countDownLatchDemo::add, i +"");}for(Thread t:threads){t.start();}try {/** 主线程阻塞等待countDown计数完毕 */countDownLatchDemo.countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(countDownLatchDemo.count);}
}
2) CyclicBarrier:

多个线程都执行到cyclicBarrier.await()后相互等待,等待的线程数达到阈值后触发CyclicBarrier对象中定义的Runnable方法,然后各自线程再继续执行他们后续的操作;

public class CyclicBarrierDemo {public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(30, ()->{System.out.println("满30线程,发车");});Thread[] threads = new Thread[30];for(int i = 0; i<30; i++){threads[i] = new Thread(()->{try {System.out.println(Thread.currentThread().getName() + "等待");/** 每个线程都阻塞在此处,等阻塞了30个线程后,这些线程再一起执行后面的操作 */cyclicBarrier.await();System.out.println(Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}});}for(Thread t:threads){t.start();}}
}
3) ReadWriteLock:

加读锁的方法多线程可以并发执行(读不会修改共享变量值),执行加写锁的方法时,必须单线程串行执行,并且要等当前的读锁全部释放后才能获取写锁,执行写锁方法(如果所有方法都加ReentrantLock那么每个线程都串行执行,效率降低)

public class ReadWriteLockDemo {private Lock lock = new ReentrantLock();private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();private Lock readLock = readWriteLock.readLock();private Lock writeLock = readWriteLock.writeLock();/**定义读方法时设置传入一个读锁 */public void write(Lock lock){lock.lock();try{System.out.println("write val");TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}/**定义写方法时设置传入一个写锁 */public void read(Lock lock){lock.lock();try {System.out.println("read val");TimeUnit.SECONDS.sleep(1);}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public static void main(String[] args) {ReadWriteLockDemo rw = new ReadWriteLockDemo();Thread[] threads = new Thread[20];Thread[] writeThreads = new Thread[3];for(int i=0; i<20; i++){new Thread(()->{// 读线程加的是读锁rw.read(rw.readLock);},"AA").start();}for(int i=0; i<3; i++){new Thread(()->{// 写线程传入的是写锁rw.write(rw.writeLock);},"BB").start();}}
}
4) Semaphore:

线程new时try中加Semaphore对象和释放Semaphore对象,Semaphore new对象时定义允许几个线程同时并发执行他们各自的操作;

作用:限流

public class SemaphoreDemo {public static void main(String[] args) {/** 允许几个线程并发执行,许可证数量 */Semaphore s = new Semaphore(10);/** 按照线程加入顺序公平获取许可证AQS实现 */Semaphore fairS = new Semaphore(10,true);for(int i=0; i<50; i++){new Thread(()->{try {/** 当前线程想要继续执行,必须从semaphore里获取一个许可 */s.acquire();Date date = new Date();SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String time = format.format(date);System.out.println(Thread.currentThread().getName() + "执行任务" + time);TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}finally {/** 当前s个线程数都执行完了,刷新一次许可证数量 */s.release();}},"AA").start();}}
}
5) Exchanger.exchange():

两个线程运行到该方法阻塞,等两个线程交换同一个变量的值后继续执行;

6) LockSupport.park() / unpark(t):

用于使指定线程停止,并在任意时候唤醒指定的线程(之前需要通过锁实现Lock.Condition.await,这里不需要锁,只需要调用该类的两个静态方法)

public class LockSupportDemo {public static void main(String[] args) {Thread t =new Thread(()->{for(int i=0; i<20; i++){if(i==5) {/** 停车 */LockSupport.park();}System.out.println(i);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("经过10s后执行unpark");/** 开车,指定让某个线程继续执行 */LockSupport.unpark(t);}}
7) lock.newCondition:

本质是将等待获取锁的线程所在的队列变成多个,一个condition就是一个队列,可以将线程分类加入到对应的队列中,根据程序逻辑精确唤醒对应condition队列里的线程。

已用Condition实现多线程生产者消费者模式:

public class ProviderConsumer {/***  实现一个容器,模拟多线程并发加入元素与取出元素*/Lock lock = new ReentrantLock();/** 专门用来唤醒生产者 */Condition provider = lock.newCondition();/** 专门唤醒消费者 */Condition consumer = lock.newCondition();// 存放产品的容器final LinkedList<Object> list = new LinkedList<>();// 最多放十个对象final int max = 10;// 当前容器中的元素个数int count = 0;public void put()  {lock.lock();try{Object o = new Object();while(count == max){try {System.out.println("容器满了");/** 所有生产者停止 */provider.await();} catch (InterruptedException e) {e.printStackTrace();}}list.add(o);++ count;System.out.println("生产中" + count);/** 通知消费者 */consumer.signalAll();}finally {lock.unlock();}}public synchronized Object get(){lock.lock();try{while(list.size() == 0){try {System.out.println("容器空了");/** 所有消费者停止 */consumer.await();} catch (InterruptedException e) {e.printStackTrace();}}Object o = list.removeFirst();count --;System.out.println("消费中" + count);/** 通知生产者 */provider.signalAll();return o;}finally {lock.unlock();}}public static void main(String[] args) {ProviderConsumer pc = new ProviderConsumer();// 消费者for(int i=0; i<10; i++){new Thread(()->{for(int j=0; j<10; j++) {System.out.println(pc.get());}},"A" + i).start();}try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}// 生产者for(int i=0; i<10; i++){new Thread(()->{for(int j=0; j<10; j++) {pc.put();}}, "B" + i).start();}}
}

五、AQS底层

ps:阅读源码的方式

1)在debug模式下阅读,程序没有运行时调用的方法与程序运行时不一定一致,因为多态的存在,运行时可能调用的是子类重写的方法(静态时可能有些接口方法还没有被实现,看不到执行逻辑);遇到一个不知名的方法就在这里打断点运行进去看看;

2)调试时画UML图(方法的调用过程)以及涉及的每个类的子类父类关系图

除了LockSupport以外,其他锁都是通过AQS底层实现的;

AQS = volatile + CAS

  • volatile:对象被锁重入的次数是由变量state表示的,其由volatile修饰;
  • CAS:尝试获取锁的线程是通过CAS的方式加入队列的,即自旋比较当前对象锁的state值以及是否是被线程自己占有;

具体AQS源码底层结构看另一篇文章;

补充在JDK9后,AQS中加入了一个VarHandle,可以用一个handle句柄指向一个对象,得到该对象的新的一个引用,然后VarHandle提供了对该对象的原子操作。比如可以作为一个int值,long值的引用,直接指向这块内存,实现线程安全的变量更改值的操作。(实现原子性的底层原理:VarHandle可以对内存操作,直接操纵二进制码,比基于反射的实现效率高,是由C实现的native方法,被CPU原语所支持)

------------------------------------------------------------------------------------------------------------------

六、底层实现总结

  • ReentrantLock:AOS — AQS — Sync — FairSync/NonFairSync — CAS — unsafe(这个类为单例,可以直接操作内存分配释放,因为底层是C实现的) — lock_if_mp
  • Atomic类:getAndIncrement — CAS — unsafe — lock_if_mp
  • Volatile:1)有序性:load/store fence ;2)可见性:MESI缓存一致性协议 + 总线机制
  • Sychronized:1)轻量级锁(CAS); 2)重量级锁:monitorenter / monitorexit / monitorexit

这篇关于Sychronized Volatile Atomic ReentrantLock工具类 AQS底层实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/577561

相关文章

Nacos注册中心和配置中心的底层原理全面解读

《Nacos注册中心和配置中心的底层原理全面解读》:本文主要介绍Nacos注册中心和配置中心的底层原理的全面解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录临时实例和永久实例为什么 Nacos 要将服务实例分为临时实例和永久实例?1.x 版本和2.x版本的区别

Python实现自动化Word文档样式复制与内容生成

《Python实现自动化Word文档样式复制与内容生成》在办公自动化领域,高效处理Word文档的样式和内容复制是一个常见需求,本文将展示如何利用Python的python-docx库实现... 目录一、为什么需要自动化 Word 文档处理二、核心功能实现:样式与表格的深度复制1. 表格复制(含样式与内容)2

python获取cmd环境变量值的实现代码

《python获取cmd环境变量值的实现代码》:本文主要介绍在Python中获取命令行(cmd)环境变量的值,可以使用标准库中的os模块,需要的朋友可以参考下... 前言全局说明在执行py过程中,总要使用到系统环境变量一、说明1.1 环境:Windows 11 家庭版 24H2 26100.4061

Python中bisect_left 函数实现高效插入与有序列表管理

《Python中bisect_left函数实现高效插入与有序列表管理》Python的bisect_left函数通过二分查找高效定位有序列表插入位置,与bisect_right的区别在于处理重复元素时... 目录一、bisect_left 基本介绍1.1 函数定义1.2 核心功能二、bisect_left 与

VSCode设置python SDK路径的实现步骤

《VSCode设置pythonSDK路径的实现步骤》本文主要介绍了VSCode设置pythonSDK路径的实现步骤,包括命令面板切换、settings.json配置、环境变量及虚拟环境处理,具有一定... 目录一、通过命令面板快速切换(推荐方法)二、通过 settings.json 配置(项目级/全局)三、

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=

java中BigDecimal里面的subtract函数介绍及实现方法

《java中BigDecimal里面的subtract函数介绍及实现方法》在Java中实现减法操作需要根据数据类型选择不同方法,主要分为数值型减法和字符串减法两种场景,本文给大家介绍java中BigD... 目录Java中BigDecimal里面的subtract函数的意思?一、数值型减法(高精度计算)1.

C#代码实现解析WTGPS和BD数据

《C#代码实现解析WTGPS和BD数据》在现代的导航与定位应用中,准确解析GPS和北斗(BD)等卫星定位数据至关重要,本文将使用C#语言实现解析WTGPS和BD数据,需要的可以了解下... 目录一、代码结构概览1. 核心解析方法2. 位置信息解析3. 经纬度转换方法4. 日期和时间戳解析5. 辅助方法二、L

使用Python和Matplotlib实现可视化字体轮廓(从路径数据到矢量图形)

《使用Python和Matplotlib实现可视化字体轮廓(从路径数据到矢量图形)》字体设计和矢量图形处理是编程中一个有趣且实用的领域,通过Python的matplotlib库,我们可以轻松将字体轮廓... 目录背景知识字体轮廓的表示实现步骤1. 安装依赖库2. 准备数据3. 解析路径指令4. 绘制图形关键

C/C++中OpenCV 矩阵运算的实现

《C/C++中OpenCV矩阵运算的实现》本文主要介绍了C/C++中OpenCV矩阵运算的实现,包括基本算术运算(标量与矩阵)、矩阵乘法、转置、逆矩阵、行列式、迹、范数等操作,感兴趣的可以了解一下... 目录矩阵的创建与初始化创建矩阵访问矩阵元素基本的算术运算 ➕➖✖️➗矩阵与标量运算矩阵与矩阵运算 (逐元