Implement singleton with volatile and synchronized

There are still many controversies about the usage of `volatile` and `synchronized`. `volatile` is not a new technology, but understanding it is very important for every professional Java developer. In addition, I did not find a good summary article or example on this topic, which prompted me to write this article.


Simply put, the variable marked by the `volatile` keyword has "visibility", whether it is a read or write operation, it will be forced to refresh to the main memory, ensuring that multiple threads can see the modified value of the variable. On the other hand, `synchronized` also provides **visibility**, by adding a mutex lock to synchronize all the code protected by the lock with the main memory. This can prevent other threads from updating the object when a thread is reading, causing the read state to be inconsistent with the actual state.


Singleton implementation: using volatile Bean pattern


Let us use a lazy loading singleton to help understand. The example uses double check locking and implements the "volatile bean mode", the code is as follows:


> Annotation: Double-checked locking mode (also known as "double-checked locking optimization", "lock hint" (Lock hint)) is a software design pattern used to reduce the overhead of competition and synchronization in concurrent systems. The double check lock mode first verifies the lock condition (the first check), and only when the lock condition is verified can the lock logic be truly implemented and the conditions are verified again (the second check). - WikiPedia


```java
public class MutableSingleton {
   private static volatile MutableSingleton INSTANCE;
   private static final Object mutex = new Object();
   private volatile boolean someFlag;
   // 这个单例包含更多可变状态

   private MutableSingleton(boolean someFlag) {
       this.someFlag = someFlag;
   }

   public static MutableSingleton getInstance() {
       MutableSingleton singleton = INSTANCE;
       if (singleton != null)
           return singleton;
       synchronized (mutex) {
           if (INSTANCE == null)
               INSTANCE = new MutableSingleton(false);
           return INSTANCE;
       }
   }

   public boolean isSomeFlag() {
       return someFlag;
   }

   public void setSomeFlag(boolean someFlag) {
       this.someFlag = someFlag;
   }
}
```


The above is a variable multi-threaded singleton implementation, where the `INSTANCE` and `someFlag` variables are both marked as `volatile`, but the latter is not included in the `synchronized` code block. Modifications to the singleton will immediately be seen by other threads (visibility). Here is the test case:


```java
public class MutableSingletonTest {
   private long counter = 0;
   public Object[] execute(Object... arguments) {
       final Timer timer = new Timer();
       timer.schedule(new TimerTask() {
           public void run() {
               MutableSingleton.getInstance().setSomeFlag(true);
               System.out.println("Timer interrupted main thread ...");
               timer.cancel();
           }
       }, 1000);
       while (!MutableSingleton.getInstance().isSomeFlag()) {
           counter++;
       };
       System.out.println("Main thread was interrupted by timer ...");
       return new Object[] { counter, MutableSingleton.getInstance().isSomeFlag() };
   }
   private class Worker implements Runnable {
       @Override
       public void run()
{
           Object[] result = execute();
           System.out.println(result[0]+"/"+result[1]);
       }
   }
   public static void main(String[] args) throws InterruptedException {
       MutableSingletonTest volatileExample = new MutableSingletonTest();
       Thread thread1 = new Thread(volatileExample.new Worker(), "Worker-1");
       thread1.start();
       Thread.sleep(5000);
   }
}
```


让我们快速了解整个程序。首先,`main()` 方法(第29行)创建了 `thread1`,在线程里执行 `execute()` 方法(第24行)。`execute()` (第5行)会新建 TimerThread(第7行)调度执行。接着,`thread1` 会循环等待(第14行),直到 `timer` 线程设置 `someFlag` 为 true(第9行)。使用 `MutableSingleton` 执行结果如下:


```shell
Timer interrupted main thread ...
Main thread was interrupted by timer ...
804404197/true
```


`timer` 线程与 `thread1` 正确同步,这是因为 `volatile` 在 `MutableSingleton` 上发挥了作用。


让单例失效


删除 `MutableSingleton` 中 `volatile` 关键字再次运行测试,结果应该是这样的(至少在我的机器上是这样的)。这意味着线程协调失效了:


```shell
Timer interrupted main thread ...
```


上面的运行结果表明 `thread1` 无法看到 `someFlag` 修改后的值。`timer` 线程把 `someFlag` 设置为 `true`,但由于 `volatile` 关键字已经删除,`thread1` 看不到更新后的值。


不使用 volatile 会怎么样?


现在让我们做一些别的测试:如果去掉 `MutableSingleton` 上的 `volatile` 关键字,给 `someFlag` 加上 `synchronized` 会怎么样呢?修改单例中的状态会对线程可见吗?下面是修改后的 `MutableSingleton` 类:


```java
public class MutableSingletonSynchronized {
   private static MutableSingletonSynchronized INSTANCE;
   private static final Object mutex = new Object();
   private boolean someFlag;

   // 这个单例包含更多可变状态
   private MutableSingletonSynchronized(boolean someFlag) {
       this.someFlag = someFlag;
   }

   public static MutableSingletonSynchronized getInstance() {
       MutableSingletonSynchronized singleton = INSTANCE;
       if (singleton != null)
           return singleton;
       synchronized (mutex) {
           if (INSTANCE == null)
               INSTANCE = new MutableSingletonSynchronized(false);
           return INSTANCE;
       }
   }

   public synchronized boolean isSomeFlag() {
       return someFlag;
   }

   public synchronized void setSomeFlag(boolean someFlag) {
       this.someFlag = someFlag;
   }
}
```


在上面的代码中,访问 `someFlag` 已标记为 `synchronized` 并且删掉了 `volatile` 关键字。再次执行测试,控制台的输出如下。注意,这次没有使用 `volatile`。


```shell
Timer interrupted main thread ...
Main thread was interrupted by timer ...
60113040/true
```


看起来线程运行一切正常。原因在于访问 `someFlag` 的方法加上了 `synchronized` 关键字,这样本地线程与主内存能正确同步。这里有一个问题:如果采用 `synchronized` 访问,还需要 `volatile` 吗?答案很明确:视情况而定。


volatile 的隐藏特性


只要包含可变状态,采用双重检查锁定模式创建单例,都建议在 `INSTANCE` 字段上使用 `volatile`。因此对于上面的示例,虽然没有使用 `volatile` 线程也能够正常工作,但是应该加上 `volatile` 关键字。这里用到了 `volatile` 的另一个“特性”,有时候被称作“一次性安全发布”。该特性解决了双重检查锁定模式带来的问题,造成问题的原因如下:


由于缺乏同步机制,既可能看到其他线程写入后的新值,也可能看到对象之前的旧值。


这是什么意思?如果不使用 `volatile`,JIT 编译器可能会改变 `INSTANCE` 字段赋值操作(`MutableSingleton` 代码第17行),重新对赋值语句排序。例如,先把静态 `INSTANCE` 字段设为包含默认值的实例,再把可变成员字段 `someFlag` 设为构造函数传入的初始值。情况不凑巧时,会出现后面的线程先进入 `null` 检查(`MutableSingleton` 第13行),这时 `INSTANCE` 的值已经不是 `null`。此时,返回给客户端的实例包含的是不完整的默认值。由于可能出现这种复杂情况,推荐下面规则:


在应用双重检查锁定模式创建单例时,务必使用 `volatile`。


理由:


当单例 `INSTANCE` 字段加上 `volatile` 关键字时,编译器不会对该字段赋值重新排序。


不可变对象


对于不可变单例,情况又有一些不同。如果单例是不可变对象,那么即使不使用 `volatile`,双重检查锁定模式的延迟初始化也能正常运行。[这是因为][1]读写不可变对象都是原子操作。因此,这里要小心确认,引用的对象是否真的不可变:


[1]:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html


对于不可变对象,在双重检查锁定模式中不强制使用 `volatile`。


volatile 没有互斥锁


使用 `volatile` 时还有一点需要注意:尽管 `volatile` 能够确保所有更改对线程都可以见,但并没有像 `synchronized` 那样增加互斥锁。这意味着代码并非线程安全。


```java
private static volatile int nextSerialNumber = 0;
public static int generateNextSerialNumber() {
  return nextSerailNumber++;
}
```


修改 `nextSerialNumber` 对所有线程可见。但是由于(++)实际上执行了读写两个操作,当多个线程调用 `generateNextSerialNumber()` 访问同一个地址时,可能会出现数据竞争。可以通过添加 `synchronized` 解决:


```java
private static int nextSerialNumber = 0;
public static synchronized int generateNextSerialNumber() {
  return nextSerailNumber++;
}
```


只要访问 `nextSerialNumber` 字段标记为 `synchronized`,就不必再使用 `volatile`。


结合 volatile 与 synchronized


综上所述,一个线程安全、性能优良的完整单例模式看起来应该像下面这样:


```java
public class MutableSingletonComplete {
   private static volatile MutableSingletonComplete INSTANCE;
   private static final Object mutex = new Object();
   private volatile boolean someFlag;
   private volatile int counter;
   // 这个单例包含更多可变状态
   private MutableSingletonComplete(boolean someFlag) {
       this.someFlag = someFlag;
   }
   public static MutableSingletonComplete getInstance() {
       MutableSingletonComplete singleton = INSTANCE;
       if (singleton != null)
           return singleton;
       synchronized (mutex) {
           if (INSTANCE == null)
               INSTANCE = new MutableSingletonComplete(false);
           return INSTANCE;
       }
   }
   public boolean isSomeFlag() {
       return someFlag;
   }
   public void setSomeFlag(boolean someFlag) {
       this.someFlag = someFlag;
   }
   public int getCounter() {
       return counter;
   }
   public synchronized void incrementCounter() {
       counter++;
   }
}
```


总结:


  • 对 `INSTANCE` 字段使用 `volatile` 关键字,应用一次性安全发布;

  • 对单例属性使用 `volatile` 关键字,保持可见性;

  • 如果修改状态不是原子操作,即使该字段声明为 `volatile`,也需要使用 `synchronized` 关键字,使用互斥锁;

  • 如果单例中的 `volatile` 属性是对象引用,要么该对象也是线程安全的实现,要么对象本身不可变。这样实现会更加完善。


好了,今天的困惑已经够多了,让我们喝杯咖啡。示例代码可以在[这里][2]找到。


希望本文的示例对系统化了解 `volatile` 有所帮助。文中内容难免有错漏,欢迎评论、提问、抛出你的想法。


[2]:https://github.com/nschlimm/playground-java8/tree/master/src/main/java/org/projectbarbel/playground/revisitevolatile



Guess you like

Origin blog.51cto.com/15082395/2590351