synchronized与volatile关键字详解

synchronized

synchronized是一种同步锁,可以保证被它修饰的方法或者代码块在任一时刻只有一个线程在执行。

如何使用synchronized关键字

    修饰实例方法 给当前实例对象加锁,进入同步代码块之前需要获得当前实例对象的锁。 修饰静态方法 给当前类加锁,会所用于类的所有实例对象,进入同步代码块之前要获得当前class的锁。 修饰代码块 指定加锁对象,对给定对象/类加锁。

synchronized关键字的底层原理

JVM底层通过获取monitor对象来实现同步方法和代码块。

其中monitorenter指令指向同步代码块的开始位置,monitorexit指令指向同步代码块的结束位置或异常处;任何一个对象都有与之对应的monitor,当一个monitor被持有后,它处于锁定状态,当线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁。

Java对象头

synchronized使用的锁存储在Java对象头中,Java对象头的Mark Word默认存储对象的HashCode、分代年龄和锁标志位。32位的JVM的Mark Word默认存储结构如下图所示: 运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化:

锁升级

锁一共有四种状态:无锁、偏向锁、轻量级锁和重量级锁(逐渐升级)。锁的优缺点对比:

锁 优点 缺点 适用场景 偏向锁 加锁和解锁不需要额外消耗,与非同步锁方法仅存在微小差距 如果线程之间存在锁竞争,会带来额外的锁撤销的消耗 单线程访问同步块 轻量级锁 竞争的线程不会产生阻塞,提高了程序响应速度 始终得不到锁的线程自旋会消耗CPU 追求响应时间,同步块执行速度非常快 重量级锁 线程竞争不使用自旋消耗CPU 线程阻塞,响应时间缓慢 追求吞吐量,同步块执行时间较长

synchronized性质

    可重入性:同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁; 不可中断性:一旦锁被其它线程占用,必须等待直到对方释放该锁。

volatile

volatile是轻量级的synchronized,它在多线程开发中保证共享变量的“可见性”。其执行成本更低,因为它不会引起线程上下文切换和调度。

volatile作用

    保证可见性 一个线程对于加了volatile关键字的数据的改动,其它线程立即可以感知到; 不保证原子性 原子性即完整性,指的是某个线程在做某个业务时,中间不可以被分割,要么同时成功要么同时失败。如何解决?——使用JUC下的AtomicInteger的getAndIncrement方法,底层原理是CAS。 禁止指令重排 JVM具有指令重排的特性,在单线程环境下不会出现问题,但是在多线程环境中会导致一个线程获得还未被初始化的实例。使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。

volatile底层原理

首先看一下Java内存模型(JMM): 线程把变量保存在本地内存中,而不是直接在主内存中进行读写,这就导致一个线程对数据的修改不能及时刷新到主内存,而此时另一个线程就已经读取了该数据,这样会发生错误。此时需要volatile关键字保证可见性。通过synchronized和lock也可以保证可见性,但是开销较大。

二者对比

    volatile是线程同步的轻量级实现,但是其只能修饰变量而synchronized能修饰方法和代码块; volatile只能保证数据的可见性不能保证原子性,而synchronized二者都能保证; volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的事多个线程访问资源的同步性。
经验分享 程序员 微信小程序 职场和发展