Java多线程并发——CAS和AQS
多核CPU、多线程的场景下,一起学习Java如何保证程序原子性,有序性,以及数据完整性等特性。
CAS
Compare And Swap
原子操作,更新之前,比较期望值,如果是期望值的话,写数据,否则不写数据,更新失败。
Java的CAS操作调用的是unsafe本地Native方法,通过使用CPU相关指令来达到原子性操作,包括多核CPU下的原子操作。
通常为保证更新成功,操作需要自旋。即不断的尝试CAS更新,直到更新成功。如AtomicInteger中的一段代码:
public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
自旋更新
public final int getAndUpdate(IntUnaryOperator updateFunction) {int prev, next;do {prev = get();next = updateFunction.applyAsInt(prev);} while (!compareAndSet(prev, next));return prev;}
ABA问题,CompareAndSwap的值从A变为B,再由B变为A,这种情况下,CAS认为值没有变,但其实是变了的,需要使用版本号来解决此问题,Atomic使用AtomicStampedReference来解决。
特点
CAS认为锁竞争不激烈,更新成功概率高,在激烈竞争的情况下,更新成功概率降低,自旋时间变长,影响服务器性能
只能保证一个共享变量的原子性操作,多个共享变量时,需要将多个共享变量合成一个共享变量AtomicReference
AQS
AbstractQueuedSychronizer
抽象队列同步,实现锁和同步机制的基础框架,RetreentLock RetreentLockReadWriteLock Semaphore CountDownLatch等实现基于AQS
AQS维护一个volatile修饰的state字段,来控制多个线程之间的独占或者共享同步状态。state值的修改通过CAS的方式。
AQS维护了一个FIFO的队列,线程获取同步状态失败时,构建一个Node并添加到队列尾部tail(通过CAS实现),并阻塞当前线程,当其他释放同步状态时,判断队列的head是否为空,不为空head节点获得锁
通过自旋的方式获得锁或者队列排队的方式获取锁。
sychronized
sychronized为隐式锁,编也就是译器自动加锁,解锁,并在异常情况下自动解锁。
sychronized使用对象锁,类锁实现,对象锁是对某一象加锁,类锁是对某一个类加锁。
类和对象的wait和notifiy方法,wait方法释放锁并等待锁,锁notifiy之后,有可能重新获得锁。
- 对静态方法加锁,等于对类加锁,影响程序性能,其他对类加锁的线程都需要等待。
- 对实例方法加锁,等于对对象加锁,影响程序性能,其他对对象加锁的线程都需要等待
使用sychronized,一般做法是定义功能单一的类或对象,使用其类锁或对象锁,对代码块临界区加锁。
临界区是指需要同步控制的代码块。
sychronized缺点
1,不可响应中断,线程请求锁时,没有打断机制,可能造成死锁
2,请求锁没有超时机制,一直阻塞等待
3,不能尝试获取锁,只能阻塞等待。(尝试获取锁,tryLock,没获取到直接返回,继续执行其他逻辑)
Lock
接口,锁的顶级接口
lock vs sychronized
优点
1,提供了tryLock,请求锁不会一直等待,引入请求锁的超时机制
2,更加灵活,可以根据代码的不同条件来决定释放锁或者请求锁。
3,lockInterruptibly,支持中断阻塞线程,避免死锁发生
缺点
显式的锁,需要手动的获得锁,关锁,处理异常情况下的锁问题
ReentrantLock
Lock的实现类,基于AQS实现
- 支持公平获取锁和非公平获取锁
公平锁加锁过程:当state=0且队列中没有等待时,尝试CAS获取锁,没有获取到锁,添加到队列中。当队列中有等待时,也加入队列中等待,直到线程变为队列Head的时候自旋获取锁。
非公平锁加锁过程:直接尝试CAS获取锁,没有获取到锁,加入到同步队列中,一次获取锁,这时的非公平体现在head和新的线程不公平竞争,但是在同步队列中的还是要依次获得锁。ReentrantLock默认是非公平锁。
private ReentrantLock lock = new ReentrantLock(); // non fair,非公平锁private ReentrantLock lock = new ReentrantLock(true); // fair,公平锁private ReentrantLock lock = new ReentrantLock(false); // no fair,非公平锁
- 尝试一次性非阻塞获取锁,提高编码灵活性
lock.tryLock();
- 支持超时机制,超过时间没有获取锁,直接返回。避免死锁。
lock.tryLock(10, TimeUnit.SECONDS);
- 支持可中断获取锁,和lock()方法获取锁的方式相比,区别在于,lockInterruptibly()获取锁的过程可以被打断,其他线程调用了该线程的interrupt方法后,该线程不再尝试获取锁,而是执行线程中断,抛出InterruptedException,而lock()比较头铁,还会一直尝试获取锁,获取锁后才执行线程中断逻辑。
lock.lockInterruptibly();
- 可以监听不同condition等待条件
condition,类似于Object和对象的,notify和wait方法,有等待和通知的功能。
如:一只单纯的牛,每天就eat,sleep,work三件事情,分别由三个线程控制,每个线程分别不断的尝试eat,sleep,work,且在每个活动中互不影响,但是牛的主人规定牛在eat之后,才能sleep,在sleep后才能work,在work之后才能eat。
public class PureCow {private ReentrantLock lock = new ReentrantLock();private Condition eatCondition = lock.newCondition();private Condition sleepCondition = lock.newCondition();private Condition workCondition = lock.newCondition();String status = "eat";public void eat(){lock.lock();System.out.println(Thread.currentThread().getName() + " thread get lock,start eat");try{if(!status.equals("eat")) {eatCondition.await();}System.out.println("cow eatting......");// 模拟 eatThread.sleep(3000);status = "sleep";sleepCondition.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public void sleep(){lock.lock();System.out.println(Thread.currentThread().getName() + " thread get lock,start sleep");try{if(!status.equals("sleep")) {sleepCondition.await();}// 模拟 sleepSystem.out.println("cow sleepping......");Thread.sleep(3000);status = "work";workCondition.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public void work(){lock.lock();System.out.println(Thread.currentThread().getName() + " thread get lock,start work");try{if(!status.equals("work")) {workCondition.await();}// 模拟 workSystem.out.println("cow working......");Thread.sleep(3000);status = "eat";eatCondition.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public static void main(String[] args) {PureCow cow = new PureCow();Thread t1 = new Thread( () -> {while(true){cow.eat();}}, "eat");Thread t2 = new Thread( () -> {while(true){cow.sleep();}}, "sleep");Thread t3 = new Thread( () -> {while(true) {cow.work();}}, "work");t1.start();t2.start();t3.start();}
}
ReentrantReadWriteLock
ReadWriteLock,维护一对关联的锁,一个是针对只读操作的读锁,一个针对写操作写锁。
读锁可以线程读操作共享,写锁线程独占。
在操作共享数据时,跟独占锁相比较,ReadWriteLock支持更高的并发。
ReentrantReadWriteLock是ReentrantLock和ReadWriteLock的结合实现
特点
- 没有强加读写优先权
- 支持公平和非公平策略,默认非公平模式
- 重入,读锁可以重入读锁,写锁可以重入写锁,读锁可以重入写锁,但是写锁不能重入读锁,
- 锁可以降级,写锁可以降级为读锁,因为读锁共享,有共享变为独有,代价比较大
读写锁的state状态,使用int类型的state的高16位标识读锁,低16位标识写锁
读锁加锁过程
- 获取当前线程,判断写锁state是否为0,不为0说明有写锁,判断持有锁的是不是当前线程,如果不是当前线程,返回-1,加锁失败。
- 判断读锁,已加锁次数小于65536,且不需要等待,尝试加锁。公平情况下队列中有等待读锁时需要等待,且非公平竞争情况下,队列中有等待的写锁需要等待。
- 加锁成功后,修改锁被持有的数量,如果是第一个持有线程,修改第一个持有锁的线程,第一个持有锁的线程的持有次数。如果不是第一个持有的线程,在线程ThreadLocal中记录持有锁的次数,返回成功
- 如果尝试加锁失败,自旋加锁。
写锁加锁过程
- 获取当前线程,如果state等于0,没有锁,尝试CAS加锁,加锁成功后设置锁的独占线程。
- 如果state不等于0,获取独占锁数量,如果等于0,说明有读锁,判断是不是当前线程,不是当前线程加锁失败,否则尝试CAS加锁,成功后设置锁的独占线程
锁降级和锁重入,读锁可以重入写锁(写锁中重新获取读锁),独占锁到共享锁比较容易,而共享锁转为独占锁比较难。 所以写锁不可以重入读锁,真实运行写锁重入读锁不会异常,而是一直卡住获取不到锁。
/*** 读锁重入写锁,写锁中重新获取读锁*/public void readReentryWrite(){writeLock.lock();try{System.out.println(Thread.currentThread().getName()+" get readLock");Thread.sleep(3000);System.out.println(Thread.currentThread().getName()+" do write");readLock.lock();System.out.println(Thread.currentThread().getName()+" do read");System.out.println(Thread.currentThread().getName()+" release readLock");}catch (Exception e){e.printStackTrace();}finally {readLock.unlock();writeLock.unlock();}}/*** 锁降级,程序的后半段,writeLock.unlock()之后,线程的锁变为读锁*/public void degrade(){writeLock.lock();try{System.out.println(Thread.currentThread().getName()+" get readLock");Thread.sleep(3000);System.out.println(Thread.currentThread().getName()+" do write");readLock.lock();writeLock.unlock();System.out.println(Thread.currentThread().getName()+" do read");System.out.println(Thread.currentThread().getName()+" release readLock");}catch (Exception e){e.printStackTrace();}finally {readLock.unlock();if (writeLock.isHeldByCurrentThread()) {writeLock.unlock();}}}
StampedLock
StampedLock
优化了ReentrantReadWriteLock,优化了RRWL的饥饿问题,如读操作很多,写操作很少时,写操作饥饿问题。
具有三种不同模式来控制读写访问,StampedLock不可重入,不支持Condition等待,支持读锁写锁转换。
StampedLock的state由版本和模式组成,获取锁的时候,返回一个标识并控制锁状态的stamp,try操作会反馈0来表示无法获取锁,锁的转换和释放需要使用stamp作为参数,如果stamp与锁的状态不匹配,则失败。
三种模式是:
写:写锁独占访问,可能阻塞等待,获取锁后返回一个stamp,用来释放锁,支持超时try的方式获取锁,如果某线程获得了写锁,其他线程就不会获取读锁,乐观读也会失败。
读:获取读锁锁,类似于ReentrantReadWriteLock,获取锁后返回一个stamp,用来释放锁。支持超时try的方式获取锁,读锁可以转化为写锁
乐观读:获取乐观读锁时,当没有写锁时返回非0值,获取锁成功。乐观读锁可以被写锁直接占用,使用乐观锁时需要校验stamp,如果已经被写锁占用,就需要转为读锁,再读取重新读数据。
Semaphore
控制共享资源的访问,同时只有有限个(根据信号量的定义)访问资源
支持公平竞争和非公平竞争
锁是特殊的信号量,同时只有一个线程访问资源。
通过自旋的方式CAS获取锁,公平模式下队列中有等待返回。
ThreadLocal
特点:
- 多线程下,以空间换时间,数据在线程的工作空间以副本的形式存在,线程之间不共享,没有多线程安全问题
- 提供线程执行任意阶段访问对象或数据的方式
缺点:
- 解决不了多线程数据共享问题
应用
服务中在ThreadLocal中存储Context上下文信息,HTTP请求和响应信息
事务中,ThreadLocal中存储数据库连接,控制事务的提交回滚
细节
强引用,正常直接引用,有强引用时,垃圾回收机制不会回收对象。
弱引用,弱引用对象会被回收,不管空间是否足够。
软引用,如果内存空间足够,不会回收软引用,否则回收软引用,软引用多用作内存敏感的高速缓存
虚引用,随时会被回收,和RefrenceQueue联合使用,跟踪垃圾回收器的回收活动
Java的Thread类中有一个ThreadLocalMap对象,其中存储着key和Value,每个ThreadLocal对应Map中的一个key,value,ThreadLocal本身是key,需要存储的对象就是value,如果需要存储多个对象,多个ThreadLocal,ThreadLocal对key是弱引用,ThreadLocal remove后就会被垃圾回收机制回收。
扩展
对比netty中的FastThreadLocal,使用FastThreadLocal时,对应的线程中存储FastThreadLocal对象的容器变为数组
需要特殊的线程实现,需要配合FastThreadLocalThread线程来使用,使用普通的Thread线程时,会变的更慢。
因为FastThreadLocal为了和Thread兼容,还增加了SlowThreadLocalMap实现。
问题
内存泄漏,方法执行完成后,栈没有引用后,ThreadLocal被回收,但是ThreadLocalMap被线程引用,不会被回收,会导致内存泄漏
ThreadLocal被static修饰后,延长了ThreadLocal的声明周期,也有可能造成不会被回收。
(完 ^_^)
Java多线程并发——CAS和AQS相关推荐
- 2021全新Java多线程并发入门到精通,一篇就能学会
目录 一, JAVA 多线程并发 1,JAVA 并发知识库 2,JAVA 线程实现/创建方式 (1) 继承 Thread 类 (2)实现 Runnable 接口. (3)ExecutorService ...
- Java 多线程 并发编程
转载自 Java 多线程 并发编程 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进 ...
- java火箭应用_从火箭发场景来学习Java多线程并发闭锁对象
原标题:从火箭发场景来学习Java多线程并发闭锁对象 从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; ...
- java 闭锁_从火箭发场景来学习Java多线程并发闭锁对象
从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; 执行后,size-这种方式来实现.但是在多线程并发的情 ...
- Java多线程并发编程--Java并发包(JUC)
Java多线程并发–Java并发包(JUC) 前言 前一篇文章中,笔者已经介绍了Java多线程的一些基础知识,但是想要成为一名中高级Java程序员还必须懂得Java并发包(JUC)的知识点,而且JUC ...
- Java多线程并发技术
Java多线程并发技术 参考文献: http://blog.csdn.net/aboy123/article/details/38307539 http://blog.csdn.net/ghsau/a ...
- Java多线程并发编程
一.线程池 1.1.什么是线程池 线程池是一种多线程的处理方式,利用已有线程对象继续服务新的任务(按照一定的执行策略),而不是频繁地创建销毁线程对象,由此提高服务的吞吐能力,减少CPU的闲置时间.具体 ...
- delay在java中有什么用_DelayQueue怎么在Java多线程并发开发中使用
DelayQueue怎么在Java多线程并发开发中使用 发布时间:2020-12-05 17:29:31 来源:亿速云 阅读:56 作者:Leah 这篇文章给大家介绍DelayQueue怎么在Java ...
- java多线程并发排序-睡眠排序大法
java多线程并发编程之创新排序法-睡眠排序大法,多线程并发排序使得排序效率更高 public class sleepSort {public static void main(String[] ar ...
最新文章
- python 微信模块_Python使用itchat模块实现简单的微信控制电脑功能示例
- 服务器每秒钟执行命令数量是什么_全国自考互联网及其应用模拟试卷(一)及答案.doc...
- 常用工具类(初级中的初级)
- C++ 继承语法及修饰符
- 工业非标设备远程运维方案
- mysql数据库巡检方案_美团 MySQL 数据库巡检系统的设计与应用
- python交通标志识别_利用pytorch实现交通标志识别
- 关于回溯模型的两种解空间树
- 过程现场总线解决方案的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
- 关于不同局域网内经Internet的P2P通信技术
- 折腾了5个多小时的OC启动与win10冲突
- SVL-VI SLAM
- 百度地图API的两种加载方式
- CREO:CREO软件中如何设置和使用各种标准模板文件(asm组件模板、drw工程图模板、prt零件模板)、零件模板的定制、创建零件自动产生绘图、绘图模板的定制之详细攻略
- java中fractions,[CF743C]Vladik and fractions
- java 编程联系_《JAVA程序设计》结对编程联系_四则运算(第一周:阶段总结)...
- win10如何开放端口
- 第19节 三个败家子(19)——史上最牛太守孙坚
- 大企业的计算机设备维护,企业计算机系统维护措施
- Android中的MVVM架构设计-实用篇(五)实现RecyclerView列表展示
热门文章
- Div+CSS教程----DivCSS布局绝对定位和浮动
- java乱码解决方法
- mysql事务日志备份_事务日志备份 (SQL Server)
- html未填写提示,文本框输入信息,未输入的文本框会提示输入,并且未输入的文本框会变红...
- c 将html导出pdf文件,将HTML页面转换为PDF文件并导出
- mybatis 不同格式日期比较大小_怎样创建一个命令函数来获得不同国家和应用程序所要求的大多数日期格式...
- python类的命名空间_Python之关于类变量的两种赋值区别详解
- github代码虚拟服务器,把github代码自动部署到服务器
- 两组回归系数差异检验_调节效应检验中的回归系数差异检验
- matlab speex的语音处理模块_基于MATLAB的条形码识别系统[GUI,可识别几十个图片]...