转载请以链接形式标明出处: 本文出自:103style的博客
Java代码 编译之后 得到 Java字节码,被 类加载器加载到JVM
中,最终 转化为汇编指令。
Java并发编程的艺术笔记
- 并发编程的挑战
- Java并发机制的底层实现原理
- Java内存模型
- Java并发编程基础
- Java中的锁的使用和实现介绍
- Java并发容器和框架
- Java中的12个原子操作类介绍
- Java中的并发工具类
- Java中的线程池
- Executor框架
volatile
volatile
是轻量级的synchronized
,被volatile
修饰的变量,在一个线程能读到这个变量被另一个线程修改之后的值。
volatile
不会引起线程上下文切换和调度。
volatile的两条实现原则
- Lock前缀指令会引起处理器缓存回写到内存.
- 一个处理器的缓存回写到内存会导致其他处理器的缓存无效.
synchronized实现同步
- 对于 普通同步方法,锁是 当前实例对象。
- 对于 静态同步方法,锁是 当前类的Class对象。
- 对于 同步方法块,锁是 Synchonized括号里配置的对象。
Synchonized在JVM里的实现原理
JVM 基于进入和退出Monitor
对象来实现方法同步和代码块同步。
- 代码块同步是使用
monitorenter
和monitorexit
指令实现的 - 方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用
monitorenter
和monitorexit
指令来实现。
monitorenter
指令是在编译后插入到同步代码块的开始位置,monitorexit
是插入到方法结束处和异常处。
JVM要保证每个monitorenter
必须有对应的monitorexit
与之配对。
任何对象都有一个monitor
与之关联,当且一个monitor
被持有后,它将处于锁定状态。线程执行到monitorenter
指令时,将会尝试获取对象所对应的monitor
的所有权,即尝试获得对象的锁。
Java对象头
synchronized
用的锁是存在Java对象头里的。在32位
虚拟机中,1字宽
等于4字节
,即32bit
。
- 数组类型,虚拟机用
3个字宽
存储对象头。 - 非数组类型,虚拟机用
2字宽
存储对象头。
锁的4种状态
级别从低到高依次是:
- 无锁状态
- 偏向锁状态
- 轻量级锁状态
- 重量级锁状态
Java中的锁介绍
原子操作的实现原理
原子(atomic
)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation
)意为“不可被中断的一个或一系列操作”。
- 处理器如何实现原子操作
- 使用总线锁保证原子性:所谓总线锁就是使用处理器提供的一个
LOCK#
信号,当一个处理器在总线上输出此信号时,其他处理器的请求将被阻塞住,那么该处理器可以独占共享内存。 - 使用缓存锁保证原子性。
以下两种情况不会使用缓存锁:
- 当处理器不支持缓存锁定。
- 当操作的数据不能被缓存在处理器内部,或操作的数据跨多个缓存行时,则处理器会调用总线锁定。
- 使用总线锁保证原子性:所谓总线锁就是使用处理器提供的一个
- Java如何实现原子操作
- 使用循环CAS实现原子操作, Java中的12个原子操作类介绍。
- CAS实现原子操作的三大问题:
- ABA问题:因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA
问题的解决思路就是 使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号
加1
,那么A→B→A
就会变成1A→2B→3A
。 原子操作类AtomicStampedReference
的compareAndSet
方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。 - 循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
- 只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。
- ABA问题:因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
- 使用锁机制实现原子操作 锁机制保证了只有获得锁的线程才能够操作锁定的内存区域。