单例必须用static修饰!!!
一、最简单、支持高并发的单例(饿汉式,不管三七二十一,上来就创建)
public class Singleton {
private static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Singleton m = Singleton.getInstance();
Singleton n = Singleton.getInstance();
System.out.println(m == n);
}
}
缺点:浪费内存,希望是在用的时候才创建对象
二、懒汉式,按需分配
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
缺点:多线程模式,可能无法保证单例
三、加锁解决懒汉式问题synchronized
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
缺点:synchronized修饰方法,锁的粒度比较大,需要缩小范围
四、缩小锁粒度
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
缺点:不能保证是单例
五、DCL单例+volatile
(double check lock + volatile)
public class Singleton {
private static volatile Singleton INSTANCE; //注意这里volatile
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
不加volatile有什么问题吗?有,因此必须要是用volatile进行修饰。
场景1:INSTANCE = new Singleton() -->变成三条汇编指令:
1) new #2 <java/lang/Singleton> ===》向操作系统申请内存
2) invokespecial #1 <java/lang/Singleton.<init>> ===》调用构造函数初始化对象
3) astore_1 ===》 将变量INSTANCE和对象建立关联
当不使用volatile修饰变量INSTANCE,可能发生指令重排,2)和 3)发生互换,即先建立关联,再初始化。此时,在建立关联之后另外一个线程调用getInstance方法,第一次判断if (INSTANCE == null),发现不为空,则线程直接返回拿到了一个未初始化的对象,造成线程不安全。
场景2:当两个线程A,B同时争抢synchronized的,线程A获取锁,创建了对象并给INSTANCE赋值,由于没有被volatile修饰,线程B判断if任为null,所以还会去创建对象,线程B把线程A的对象覆盖了,出现了线程不安全。
volatile作用:线程可见性和禁止指令重排