Volatile
单例设计模式的本质在于构造方法被私有化, 而后通过类内部的 static 方法取得实例化对象, 而单例设计有两种模式: 懒汉式(使用时才进行实例化对象), 饿汉式(在类加载时候进行对象实例化).
范例: 观察懒汉式单例设计模式的问题
package com.beyond.dhl;
class Singleton {
private static Singleton instance; // 懒汉式所以不会进行实例化对象
private Singleton() {
System.out.println("构造方法:" + Thread.currentThread().getName());
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Test {
public static void main(String[] args) {
new Thread(() -> Singleton.getInstance(), "线程A").start();
new Thread(() -> Singleton.getInstance(), "线程B").start();
new Thread(() -> Singleton.getInstance(), "线程C").start();
new Thread(() -> Singleton.getInstance(), "线程D").start();
new Thread(() -> Singleton.getInstance(), "线程E").start();
}
}
这个时候, 我们发现一个神奇的情况, 单例设计不再是单例了, 而变成了多个重复的实例化对象的类.
造成这种问题的关键是线程的不同步的问题. 有可能有多个线程同时进入到了getInstance()方法, 那么就有可能实例化多个对象.
那么这个时候可能想到的第一个解决方案: 使用 synchronized 同步. 代码下修改如下:
现在问题的确是解决了, 因为同步有了锁. 但是这个代码出现了很明显的性能瓶颈, 因为如果在高并发的访问状态下, 本操作就直接导致系统性能下降.
那么这个时候就必须依靠 volatile 来解决此类问题(该关键字不仅仅是一个同步的问题)
volatile 关键字的区别:
- 如果现在没有使用 volatile 关键字定义的变量, 则操作的时候使用的是副本进行的处理, 副本和原始数据之间的同步就需要产生延迟, 而所谓的线程不同步也在于此.
volatile 关键字定义的变量表示将直接使用原始数据进行处理, 更改后立即生效.
如果要保证性能高, 则getInstance()方法上就不能使用 synchronized 同步. 修改如下:
package com.beyond.dhl;
class Singleton {
private volatile static Singleton instance; // 懒汉式所以不会进行实例化对象
private Singleton() {
System.out.println("构造方法:" + Thread.currentThread().getName());
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
public class Test {
public static void main(String[] args) {
new Thread(() -> Singleton.getInstance(), "线程A").start();
new Thread(() -> Singleton.getInstance(), "线程B").start();
new Thread(() -> Singleton.getInstance(), "线程C").start();
new Thread(() -> Singleton.getInstance(), "线程D").start();
new Thread(() -> Singleton.getInstance(), "线程E").start();
}
}