单例模式的七种实现-Singleton(Java实现)

1. 饿汉式

实现代码:

public class Singleton {
    private Singleton() {
    }

    private static Singleton singleton = new Singleton();

    public static Singleton getInstance() {
        return singleton;
    }
}

验证一下:

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);
        // true
    }

 

如果用反射, 是否仍然是单例:

结果是反射破坏了单例

    public static void main(String[] args) throws Exception {
        // 自定义单例方法获取
        Singleton s1 = Singleton.getInstance();

        // 反射获取
        Constructor constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton s2 = (Singleton) constructor.newInstance();

        System.out.println(s1 == s2);
        //false
    }

2. 懒汉式

将上面的饿汉式改为懒汉式:

public class Singleton {
    private Singleton() {
    }

    private static Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
} 

 

验证一下:

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);
        // true
    }

 

不过这是一种线程不安全的单例实现.

我们在Singleton中加上sleep来模拟一下线程切换:

public class Singleton {
    private Singleton() {
    }

    private static Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleton = new Singleton();
        }
        return singleton;
    }
}

 验证一下线程不安全:

public class Main3 {
    private static LinkedBlockingQueue<Singleton> singletons = new LinkedBlockingQueue<>();
    public static void main(String[] args) throws Exception{
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for(int i= 0;i<100;i++){
            threadPool.execute(()->{
                singletons.offer(Singleton.getInstance());
            });
        }


        Singleton basic = singletons.take();
        while(basic==singletons.take()){
            System.out.println("continue");
            continue;
        }

        System.out.println("走到这里说明单例失败");
    }
}

 

3. 懒汉式+同步方法

public class Singleton {
    private Singleton() {
    }

    private static Singleton singleton;

    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

 验证一下:

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();

        System.out.println(s1 == s2);
        // true
    }

  由于是同步, 所以同步方法内不会出现多线程执行的情况.

4. 懒汉式+双重校验锁

因为上面那种会每次进时都会进行同步锁, 很浪费性能, 所以在加锁之间先进行校验

public class Singleton{
    private Singleton() {
    }

    private static Singleton singleton;

    public static Singleton getInstance() {
        if (singleton==null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

 验证一下性能:

能明显看出来性能差距...5千倍...

同步方法, 即直接在方法声明处加了Synchronize的情况:

    public static void main(String[] args){
        long start = System.currentTimeMillis();
        for(int i= 0;i<999999999;i++){
            Singleton s = Singleton.getInstance();
        }
        System.out.println(System.currentTimeMillis()-start);
    }

 

双重校验锁:

    public static void main(String[] args){
        long start = System.currentTimeMillis();
        for(int i= 0;i<999999999;i++){
            Singleton s = Singleton.getInstance();
        }
        System.out.println(System.currentTimeMillis()-start);
    }

 

5. 懒汉式+双重校验锁+防止指令重拍

看似简单的一段赋值语句:instance = new Singleton(); 其实JVM内部已经转换为多条指令:

memory = allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance = memory; //3:设置instance指向刚分配的内存地址

但是经过重排序后如下:

memory = allocate(); //1:分配对象的内存空间

instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化

ctorInstance(memory); //2:初始化对象

可以看到指令重排之后,instance指向分配好的内存放在了前面,而这段内存的初始化被排在了后面,在线程A初始化完成这段内存之前,线程B虽然进不去同步代码块,但是在同步代码块之前的判断就会发现instance不为空,此时线程B获得instance对象进行使用就可能发生错误。

加上volatile关键字:

public class Singleton {
    private Singleton() {
    }

    private volatile static Singleton singleton;

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

 验证一下性能:

能明显看出来加了volatile后对性能的影响, 由之前的5, 变为了302...

性能下降了, 但是相比于上面的双重校验锁, 更保证了线程安全.

    public static void main(String[] args){
        long start = System.currentTimeMillis();
        for(int i= 0;i<999999999;i++){
            Singleton s = Singleton.getInstance();
        }
        System.out.println(System.currentTimeMillis()-start);
    }

 

6. 静态内部类

这也是一种很好的实现方式, 不仅懒加载, 还保证了线程安全, 性能也很好, 实现起来也很简单

public class Singleton {
    private static class LazyHolder {
        private static final Singleton instance = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return LazyHolder.instance;
    }
}

 验证一下性能:

public static void main(String[] args){
        long start = System.currentTimeMillis();
        for(int i= 0;i<999999999;i++){
            Singleton s = Singleton.getInstance();
        }
        System.out.println(System.currentTimeMillis()-start);
    }

 

7. 枚举

个人对枚举类型的理解还有限, 有待学习....

public enum Singleton {
    INSTANCE;

    private String name;

    Singleton() {
        this.name = "king";
    }
    public static Singleton getInstance() {
        return INSTANCE;
    }

    public String getName() {
        return this.name;
    }
}

 验证一下性能:

    public static void main(String[] args){
        long start = System.currentTimeMillis();
        for(int i= 0;i<999999999;i++){
            Singleton s = Singleton.getInstance();
        }
        System.out.println(System.currentTimeMillis()-start);
    }

 

 

posted @ 2018-05-06 15:23  GoldArowana  阅读(813)  评论(0编辑  收藏  举报