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

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念