文章目录
目录
前言
主要介绍了Java虚拟机的线程安全与锁优化。
一、线程安全
- 定义
- 多线程访问一个共享资源时,代码本身封装了所有必要的正确性同步保障,调用者无需考虑多线程下的调用问题,不需要自己实现任何同步手段,都可以获得正确的结果,可以被称为绝对的线程安全
- 分类
- 不可变
- final修饰的基本数据类型,final + private修饰对象中的成员变量和方法,所有该类型数据都只在常量池中存在一份且不可变,final和private还可以防止继承重写导致的局部变量引用暴露问题
- 每次写操作都不会作用在原对象上,而是操作在一个新的复制值上,多线程写写、读写并发(读读会共享)不会导致共享同一个对象(类似JUC中的CopyOnWrite,但是其中写写也不能并发)
- 绝对线程安全
- 实现不论运行时环境如何,调用者都不需要任何额外的同步措施,就可以实现多线程安全并发共享访问同一个对象
- 相对线程安全
- 单次调用线程安全,组合连续调用时,调用端需要采取额外的同步手段(线程安全类的方法单独使用安全,组合使用不安全)
- 线程兼容
- 对象本身不安全,调用端采取同步手段后,可以实现在多线程情况下安全访问
- 线程对立
- 多线程情况下,无论调用端是否采用同步手段,都无法实现线程安全,应当避免使用
- 不可变
- 实现
- 互斥阻塞同步
- synchronize、ReentrantLock(等待可中断、可实现公平锁、多个条件变量)、信号量Semaphore(一个共享资源被有限个线程共享访问)、ReentrantReadWriteLock
- 悲观思想:一个线程持有锁后,其他线程不允许获取锁做任何操作
- 非阻塞同步
- 如何实现
- CAS+volatile实现基于冲突检测的乐观锁,出现多线程竞争时,修改失败则反复重试直到修改成功
- CAS { 第一个参数=待修改值的地址;第二个参数=待修改值原始值;第三个参数=待修改值修改后值 } 通过地址找到真实值与2号参数对比,相同修改为3号参数,否则一直循环重试,会返回原始值
- 实现要求
- 基于硬件指令集的发展,要求操作与冲突检测在硬件层面只需要一条处理器指令,具备原子性
- 细节
- Unsafe类
- CAS基于Unsafe类,该类不提供给用户使用,用户使用CAS操作时,需要通过反射手段突破Unsafe类的访问权限
- ABA问题
- CAS操作无法感知到待修改变量中间状态的变化(A->B->A无法感知B的存在),引入AtomicStampedReference、AtomicMarkedReferense每次改变都改变标记
- Unsafe类
- 如何实现
- 无同步方案
- 不涉及共享数据的方法代码天生线程安全
- 分类
- 可重入代码
- 方法的返回结果是可以预测的,只要输入相同,一定会得到相同输出
- 不涉及共享的数据(全局变量、堆中数据),不调用非可重入的方法
- 方法可重入一定线程安全,线程安全不一定可重入
- 线程本地存储
- 共享数据的可见性范围限制在同一个线程之中
- 通过java.lang.ThreadLocal实现某个变量被某个线程独享的本地存储功能
- 可重入代码
- 互斥阻塞同步
二、高效并发-锁优化
- 自旋锁与自适应锁
- 得不到锁时,不直接阻塞而是自旋重复请求锁一段时间,后升级为自适应调节自旋时间
- 临界区代码块执行速度很快时,频繁阻塞导致的上下文切换会导致效率降低
- 锁消除
- 及时编译器会编译优化掉一些不存在共享数据竞争的锁,提高效率
- 锁粗化
- 一系列零碎操作都对一个对象重复上锁时,将上锁范围扩大到包含整个操作序列
- 轻量级锁
- 只在栈中记录Lock Record(与对象头中Mark Word交换位置),不为对象关联Monitor,出现多线程同时竞争锁时升级为重量级锁,为对象关联Monitor
- 无竞争情况,用CAS消除同步使用的互斥量
- 偏向锁
- 在对象头Mark Word中记录第一次访问锁的线程ID,使得锁只能被该线程访问,出现别的线程访问锁(不是同时竞争)会升级为轻量级锁,出现同时竞争锁升级为重量级锁
- 无竞争情况,消除同步操作(CAS也不使用)
- 调用hashcode后,对象头空间不够,无法使用偏向锁
- 优化
- 批量重偏向
- 偏向锁撤销升级为轻量级锁太多次后,后面需要撤销的偏向锁,可以直接重新偏向第二次访问锁的线程
- 偏向锁撤销
- 偏向锁撤销升级、重偏向太多次(太多线程想访问锁),撤销偏向锁,直接上轻量级锁,出现同时竞争升级为重量级锁
- 批量重偏向