单例设计模式

来源:互联网 发布:mysql insert慢 io 编辑:程序博客网 时间:2024/05/21 11:26

–饿汉式–
饿汉式是线程安全的, 因为在创建类的同时就创建好了一个静态对象, 以后不会改变

public class Singleton {

//私有化构造函数private Singleton() {}private static Singleton instance = new Singleton();//静态工厂方法public static Singleton getInstance() {    return instance;}

}

–懒汉式–
懒汉式是需要的时候才创建对象, 所以存在多线程安全问题

1.懒汉式第一种(未决解多线程安全)

public class Singleton {

private Singleton() {}private static Singleton instance = null;//静态工厂方法public static Singleton getInstance() {    if(instance == null) {        instance = new Singleton();    }    return instance;}

}

2.懒汉式第二种(决解线程安全问题)(但其实还存在细微的线程安全问题)

public class Singleton {

private Singleton() {}private static Singleton instance = null;public static Singleton getInstance() {    //这里再加一个判断, 提高性能, 避免线程阻塞    if (instance == null) {        //加锁        synchronized (Singleton.class) {            if (instance == null) {                instance = new Singleton();            }        }    }    return instance;}

}

站在JVM编辑器的角度看看, 这里涉及到 JVM指令重排
指令重排是什么呢?
参考博客 http://blog.csdn.net/pxg943055021/article/details/69525035
在举个简单的例子例如 instance = new Singleton()
就会被编辑器便编译成如下JVM指令
memory = allocate(); //1. 分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance = memory; //3. 设置instance指向刚分配的内存地址

上面操作2依赖于操作1,但是操作3并不依赖于操作2, 所以JVM是可以针对它们进行指令的优化重排序的, 经过重排序后如下
memory = allocate(); //1. 分配对象的内存空间
instance = memory; //3. 设置instance指向刚分配的内存地址
ctorInstance(memory); //2. 初始化对象

假设有线程A, B
当线程A执行完1, 3时 instance对象还未被完成初始化, 但instance不在是null, 假如此时B线程抢到执行权
判断if(instance == null)为false, 就会直接返回一个未经过上述2过程的对象, 是一个未被初始化的对象, 就会发生问题
所以下面给出决解方案

3.懒汉式第三种
这里引入volatile

public class Singleton {

private Singleton() {}//这里加入volatileprivate static volatile Singleton instance = null;//静态工厂方法private static Singleton getInstance() {    if (instance == null) {        synchronized (Singleton.class) {            if (instance == null) {                instance = new Singleton();            }        }    }    return instance;}

}

什么是volatile
volatile是一个类型修饰符(type specifier),就像大家更熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile可以防止指令重排
简单说就是当线程执行instance = new Singleton()
能保证JVM编译器就会按照下面的顺序来执行
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
此时instance要么指向null 要么就是完整的初始化状态

4.懒汉式第四种

用静态内部类实现单例模式

public class Singleton {

private Singleton() {}private static class LazyHolder {    private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {    return LazyHolder.INSTANCE;}

}

该对象初始化, 并不是在类Singleton被加载时初始化, 而是在调用getInstance()方法时初始化,也是懒加载的一种
该方式既实现了线程安全,又避免了同步带来的性能影响
以上所有方法都无法防止使用反射来重复构建对象

5.第五种(推荐)
使用枚举实现单例, 可以防止反射构造对象, 还可以保证线程安全 只是该方法是非懒加载
该单例对象是在枚举类被加载的时候就进行初始化的

public enum Singleton {
INSTANCE;
}

public static void main(String[] args) {
Singleton s1 = Singleton.INSTANCE;
Singleton s2 = Singleton.INSTANCE;
System.out.println(s1.equals(s2)); //true
}

原创粉丝点击