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 pyinstaller实现图形化打包工具

《Pythonpyinstaller实现图形化打包工具》:本文主要介绍一个使用PythonPYQT5制作的关于pyinstaller打包工具,代替传统的cmd黑窗口模式打包页面,实现更快捷方便的... 目录1.简介2.运行效果3.相关源码1.简介一个使用python PYQT5制作的关于pyinstall

使用Python实现大文件切片上传及断点续传的方法

《使用Python实现大文件切片上传及断点续传的方法》本文介绍了使用Python实现大文件切片上传及断点续传的方法,包括功能模块划分(获取上传文件接口状态、临时文件夹状态信息、切片上传、切片合并)、整... 目录概要整体架构流程技术细节获取上传文件状态接口获取临时文件夹状态信息接口切片上传功能文件合并功能小

python实现自动登录12306自动抢票功能

《python实现自动登录12306自动抢票功能》随着互联网技术的发展,越来越多的人选择通过网络平台购票,特别是在中国,12306作为官方火车票预订平台,承担了巨大的访问量,对于热门线路或者节假日出行... 目录一、遇到的问题?二、改进三、进阶–展望总结一、遇到的问题?1.url-正确的表头:就是首先ur

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

JAVA利用顺序表实现“杨辉三角”的思路及代码示例

《JAVA利用顺序表实现“杨辉三角”的思路及代码示例》杨辉三角形是中国古代数学的杰出研究成果之一,是我国北宋数学家贾宪于1050年首先发现并使用的,:本文主要介绍JAVA利用顺序表实现杨辉三角的思... 目录一:“杨辉三角”题目链接二:题解代码:三:题解思路:总结一:“杨辉三角”题目链接题目链接:点击这里

基于Python实现PDF动画翻页效果的阅读器

《基于Python实现PDF动画翻页效果的阅读器》在这篇博客中,我们将深入分析一个基于wxPython实现的PDF阅读器程序,该程序支持加载PDF文件并显示页面内容,同时支持页面切换动画效果,文中有详... 目录全部代码代码结构初始化 UI 界面加载 PDF 文件显示 PDF 页面页面切换动画运行效果总结主

SpringBoot实现基于URL和IP的访问频率限制

《SpringBoot实现基于URL和IP的访问频率限制》在现代Web应用中,接口被恶意刷新或暴力请求是一种常见的攻击手段,为了保护系统资源,需要对接口的访问频率进行限制,下面我们就来看看如何使用... 目录1. 引言2. 项目依赖3. 配置 Redis4. 创建拦截器5. 注册拦截器6. 创建控制器8.

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

Springboot使用RabbitMQ实现关闭超时订单(示例详解)

《Springboot使用RabbitMQ实现关闭超时订单(示例详解)》介绍了如何在SpringBoot项目中使用RabbitMQ实现订单的延时处理和超时关闭,通过配置RabbitMQ的交换机、队列和... 目录1.maven中引入rabbitmq的依赖:2.application.yml中进行rabbit