volatile 关键字
volatile 关键字可以保证变量的可见性,表示变量是共享且不稳定的,每次使用它都到主存中进行读取。volatile 关键字可以保证变量的可见性,但是不能保证原子性,synchronized 关键字二者都可以保证。
volatile 关键字有两个作用:
- 保证变量的可见性。
- 防止 JVM 的指令重排。
单例模式
单例模式的实现如下,其中单例的对象 uniqueInstance 使用了关键字 volatile 进行修饰。uniqueInstance = new Singleton();
代码分为三步执行:
- 为
uniqueInstance
分配内存空间。 - 初始化
uniqueInstance
。 - 将
uniqueInstance
指向分配的内存地址。
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance()
后发现 uniqueInstance
不为空,因此返回 uniqueInstance
,但此时 uniqueInstance
还未被初始化。
使用 volatile 关键字可以防止上述这种指令重排的情形。
1 | public class Singleton { |
synchronized 关键字
synchronized 关键字主要解决的是多个线程之间访问资源的互斥性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
synchronized 关键字可以给 Class 类或者对象实例上锁,当 synchronized 关键字修饰 static 静态方法或者 synchronized(class)
时,都是给 Class 类上锁;当 synchronized 关键字修饰实例方法或者 synchronized(object)
时,都是给实例对象上锁。
构造方法本来就是线程安全的,所以不能使用 synchronized 修饰。
底层原理
synchronized
同步语句块的实现使用的是 monitorenter
和 monitorexit
指令(字节码指令),其中 monitorenter
指令指向同步代码块的开始位置,monitorexit
指令则指明同步代码块的结束位置。
synchronized 是可重入锁,在使用 synchronized 关键字上锁时,获取到锁会使得锁计数器加一。
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。
不过两者的本质都是对对象监视器 monitor 的获取。