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

相关文章

Python实现AVIF图片与其他图片格式间的批量转换

《Python实现AVIF图片与其他图片格式间的批量转换》这篇文章主要为大家详细介绍了如何使用Pillow库实现AVIF与其他格式的相互转换,即将AVIF转换为常见的格式,比如JPG或PNG,需要的小... 目录环境配置1.将单个 AVIF 图片转换为 JPG 和 PNG2.批量转换目录下所有 AVIF 图

Pydantic中model_validator的实现

《Pydantic中model_validator的实现》本文主要介绍了Pydantic中model_validator的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录引言基础知识创建 Pydantic 模型使用 model_validator 装饰器高级用法mo

AJAX请求上传下载进度监控实现方式

《AJAX请求上传下载进度监控实现方式》在日常Web开发中,AJAX(AsynchronousJavaScriptandXML)被广泛用于异步请求数据,而无需刷新整个页面,:本文主要介绍AJAX请... 目录1. 前言2. 基于XMLHttpRequest的进度监控2.1 基础版文件上传监控2.2 增强版多

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

Docker镜像修改hosts及dockerfile修改hosts文件的实现方式

《Docker镜像修改hosts及dockerfile修改hosts文件的实现方式》:本文主要介绍Docker镜像修改hosts及dockerfile修改hosts文件的实现方式,具有很好的参考价... 目录docker镜像修改hosts及dockerfile修改hosts文件准备 dockerfile 文

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进