Dawn's Blogs

分享技术 记录成长

0%

Java面试之并发编程 (2) volatile和synchronized关键字

volatile 关键字

volatile 关键字可以保证变量的可见性,表示变量是共享且不稳定的,每次使用它都到主存中进行读取。volatile 关键字可以保证变量的可见性,但是不能保证原子性,synchronized 关键字二者都可以保证。

volatile 关键字有两个作用:

  1. 保证变量的可见性
  2. 防止 JVM 的指令重排

单例模式

单例模式的实现如下,其中单例的对象 uniqueInstance 使用了关键字 volatile 进行修饰。uniqueInstance = new Singleton(); 代码分为三步执行:

  1. uniqueInstance 分配内存空间。
  2. 初始化 uniqueInstance
  3. uniqueInstance 指向分配的内存地址。

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

使用 volatile 关键字可以防止上述这种指令重排的情形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {

private volatile static Singleton uniqueInstance;

private Singleton() {
}

public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

synchronized 关键字

synchronized 关键字主要解决的是多个线程之间访问资源的互斥性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

synchronized 关键字可以给 Class 类或者对象实例上锁,当 synchronized 关键字修饰 static 静态方法或者 synchronized(class) 时,都是给 Class 类上锁;当 synchronized 关键字修饰实例方法或者 synchronized(object) 时,都是给实例对象上锁。

构造方法本来就是线程安全的,所以不能使用 synchronized 修饰。

底层原理

synchronized 同步语句块的实现使用的是 monitorentermonitorexit 指令(字节码指令),其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

synchronized 是可重入锁,在使用 synchronized 关键字上锁时,获取到锁会使得锁计数器加一。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

不过两者的本质都是对对象监视器 monitor 的获取。