Java 并发机制的实现原理
volatile 关键字
被 volatile 声明的变量,Java内存模型会保证所有线程看到这个变量的值是一样的,在 Java 并发机制中,volatile 是比 synchronize 更轻量级的实现,主要原因是 volatile 不会引起线程上下文切换。
被 volatile 声明的变量,底层实现上会发生下面两件事:
1.将处理器缓存行的数据写回内存
2.将其他缓存了该内存数据的缓存行置为无效
这样就可以保证每个线程看到的同一变量的值一致。
volatie 不能保证原子性
synchronize 关键字
Sychronize 关键字确保对象都能拥有锁,线程访问对象时必须先拥有该对象的锁,退出或者抛出异常时必须释放对应的锁,sychronized 关键字加在普通方法上表示锁住的是当前实例对象,加在静态方法上表示锁住的是单前类 class 对象,加在同步方法块上表示锁住的是块中的对象。
Sychronize的 Jvm 底层实现
每一个对象都有一个 monitor 与之对应,当执行到同步方法或者同步方法块时,线程会尝试获取对象的 monitor,
即执行 monitorenter 指令,获取对象的锁,在方法结束处或者异常处执行 monitorexit 指令,释放锁。
偏向锁,轻量级锁,重量级锁
Java 中锁有四种状态即:无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁,锁会随竞争逐级递增,但是不会降级。
1.偏向锁
当线程访问同步方法获取到对应的锁,会在对象的对象头和栈帧的锁记录里记录当前线程的线程id,当该线程下一次进入该同步方法时会测试一下该对象头的记录了该线程id的偏向锁,若存在,即不需要进行 cas 操作即可获得该对象的锁。若没有记录该线程id,则会去查看对象头中偏向锁的标识是否设置为1,若是则将对象头中偏向锁指向单前线程,若不是,就进行 cas 竞争。
偏向锁等到竞争出现才会释放锁。
2.轻量级锁
线程会尝试将对象头中 Mark Word 复制到线程的锁记录中,然后尝试用 cas 将对象头中 Mark Word 指向为线程锁记录中指针,成功即获得锁,失败则让其他线程竞争,并自旋。释放锁时,会使用 cas 将 Mark Word 替换回对象头中,若失败则说明存在竞争,改锁将膨胀为重量级锁。
3.重量级锁
轻量级锁在膨胀为重量级锁后,参与竞争的线程将会被阻塞,当持有该锁的线程释放后,被阻塞的线程将会被唤醒,进行新一轮的竞争。