java单例写法以及各种毛病

单例必须用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作用:线程可见性和禁止指令重排

《多线程可见性(java c语言)》

猜你喜欢

转载自blog.csdn.net/xxb249/article/details/115483907