首页 > 其他 > 详细

002-创建型-03-单例模式(Singleton)【7种】

时间:2019-07-06 14:15:38      阅读:101      评论:0      收藏:0      [点我收藏+]

一、概述

1.1、由来

  单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:

  1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

  2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。

  3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

1.2、两种创建模式对比  

  饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。

  懒汉式是典型的时间换空间,延迟加载,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

二、详细说明

2.1、饿汉模式

  它的特点是加载类的时候比较慢,但运行时获得对象的速度比较快。它从加载到应用结束会一直占用资源。程序初始化的时候初始化单例。

public class EagerSingleton {
    //饿汉单例模式
    //在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快
    private static EagerSingleton instance = new EagerSingleton();//静态私有成员,已初始化

    private EagerSingleton() {
        //私有构造函数
        System.out.println("new EagerSingleton");
    }

    //静态,不用同步(类加载时已初始化,不会有多线程的问题)
    public static EagerSingleton getInstance() {
        return instance;
    }
}

 

测试

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 100; i++) {
            fixedThreadPool.submit(() -> {
                EagerSingleton instance = EagerSingleton.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
        System.out.println("生成类数:"+setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:"+s);
        }
    }

 

 

 

结果

new EagerSingleton
耗时:73ms
生成类数:1
hashcode:1512308980

 

2.2、懒汉模式【后创建】  

  它的特点是运行时获得对象的速度比较慢,但加载类的时候比较快。它在整个应用的生命周期只有一部分时间在占用资源。

2.2.1、方式一、synchronized锁机制

版本一、初始版本【不可以,没有加锁】

技术分享图片
public class Singleton001 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static Singleton001 instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton001() {
        System.out.println("new Singleton001");
    }

    /* 静态工程方法,创建实例 */
    public static Singleton001 getInstance() {
        if (instance == null) {
            instance = new Singleton001();
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve() {
        return instance;
    }
}
View Code

 

  会有多线程问题。

测试

技术分享图片
    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(100);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 100; i++) {
            fixedThreadPool.submit(() -> {
                Singleton001 instance = Singleton001.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
        System.out.println("生成类数:"+setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:"+s);
        }
    }
View Code

 

 

 

输出

new Singleton001
new Singleton001
new Singleton001
new Singleton001
耗时:72ms
生成类数:4
hashcode:158094720
hashcode:1046297923
hashcode:1303744419
hashcode:1572373644

 

 

版本二、synchronized同步方法迭代【可以,但是效率比较低,不推荐】

public class Singleton002 {
    
    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static Singleton002 instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton002() {
    }

    /* 静态工程方法,创建实例 */
    public static synchronized Singleton002 getInstance() {
        if (instance == null) {
            instance = new Singleton002();
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve() {
        return instance;
    }
}

但是,synchronized关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,

测试:

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 50; i++) {
            fixedThreadPool.submit(() -> {
                Singleton002 instance = Singleton002.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
        System.out.println("生成类数:"+setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:"+s);
        }
    }

 

输出

new Singleton002
耗时:67ms
生成类数:1
hashcode:1052225082

 

这种实现方式的运行效率会很低。同步方法效率低。

版本三、同步代码块synchronized迭代【不可以,创建和赋值是两步操作,没锁住】

事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:

技术分享图片
public class Singleton003 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static Singleton003 instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton003() {
        System.out.println("new Singleton003");
    }

    /* 静态工程方法,创建实例 */
    public static Singleton003 getInstance() {
        if (instance == null) {
            synchronized (Singleton003.class) {
                instance = new Singleton003();//
            }
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve() {
        return instance;
    }
}
View Code

 

 

 

测试

技术分享图片
   @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(500);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 500; i++) {
            fixedThreadPool.submit(() -> {
                Singleton003 instance = Singleton003.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
        System.out.println("生成类数:"+setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:"+s);
        }
    }
View Code

 

 

输出

new Singleton003
new Singleton003
耗时:68ms
生成类数:2
hashcode:158094720
hashcode:1961726163

 

这样的方法进行代码块同步,代码的运行效率是能够得到提升,但是却没能保住线程的安全性。

在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。

版本四、同步代码块synchronized迭代,增加一次判断【可以】

public class Singleton004 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    private static Singleton004 instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton004() {
        System.out.println("new Singleton004");
    }

    /* 静态工程方法,创建实例 */
    public static Singleton004 getInstance() {
        if (instance == null) {
            synchronized (Singleton004.class) {
                if (instance == null) {
                    instance = new Singleton004();
                }
            }
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve() {
        return instance;
    }
}

 

测试

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 50; i++) {
            fixedThreadPool.submit(() -> {
                Singleton004 instance = Singleton004.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("生成类数:" + setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:" + s);
        }
    }

 

输出

new Singleton004
耗时:63ms
生成类数:1
hashcode:1052225082

 

版本五、 同步代码块synchronized迭代,同时增加volatile,双锁DCL【可以】

public class Singleton005 {

    /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */
    volatile private static Singleton005 instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton005() {
        System.out.println("new Singleton005");
    }

    /* 静态工程方法,创建实例 */
    public static Singleton005 getInstance() {
        if (instance == null) {
            synchronized (Singleton005.class) {
                if (instance == null) {
                    instance = new Singleton005();
                }
            }
        }
        return instance;
    }

    /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
    public Object readResolve() {
        return instance;
    }
}

 

测试

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 500; i++) {
            fixedThreadPool.submit(() -> {
                Singleton005 instance = Singleton005.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("生成类数:" + setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:" + s);
        }
    }

 

输出 

new Singleton005
耗时:63ms
生成类数:1
hashcode:1961726163

 

2.2.2、方式二、静态内置类实现单例模式【可以】

public class Singleton006 {

    /* 私有构造方法,防止被实例化 */
    private Singleton006() {
        System.out.println("new Singleton006");
    }

    /* 静态工程方法,创建实例 */
    public static Singleton006 getInstance() {
        return SingletonFactory.instance;
    }

    //内部类
    private static class SingletonFactory {
        private static Singleton006 instance = new Singleton006();
    }


    //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉
    protected Object readResolve() throws ObjectStreamException {
        System.out.println("调用了readResolve方法!");
        return SingletonFactory.instance;
    }
}

 

 

测试

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 500; i++) {
            fixedThreadPool.submit(() -> {
                Singleton006 instance = Singleton006.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("生成类数:" + setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:" + s);
        }
    }

 

输出

new Singleton006
耗时:67ms
生成类数:1
hashcode:752816814

 

看似完美,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

2.2.3、方式三、使用static代码块实现单例【可以】

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。

public class Singleton007 {

    private static Singleton007 instance = null;

    /* 私有构造方法,防止被实例化 */
    private Singleton007() {
        System.out.println("new Singleton007");
    }

    static {
        instance = new Singleton007();
    }

        /* 静态工程方法,创建实例 */
    public static Singleton007 getInstance() {
            return instance;
    }
}

 

测试

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 500; i++) {
            fixedThreadPool.submit(() -> {
                Singleton007 instance = Singleton007.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("生成类数:" + setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:" + s);
        }
    }

 

输出

new Singleton007
耗时:67ms
生成类数:1
hashcode:1046297923

 

看似完美,但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。

2.2.4、方式四、使用enum代码块实现单例【可以】

枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用

public class Singleton008 {

    private enum MyEnumSingleton{
        singletonFactory;

        private Singleton008 instance;

        private MyEnumSingleton(){//枚举类的构造方法在类加载是被实例化
            instance = new Singleton008();
        }

        public Singleton008 getInstance(){
            return instance;
        }
    }


    /* 私有构造方法,防止被实例化 */
    private Singleton008() {
        System.out.println("new Singleton008");
    }
    
    public static Singleton008 getInstance(){
        return MyEnumSingleton.singletonFactory.getInstance();
    }
}

 

测试

    @Test
    public void getInstance() {
        long start = System.currentTimeMillis();
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(50);
//        Set<Integer> setObj = new HashSet<>(); //线程并发问题
        ConcurrentSkipListSet<Integer> setObj = new ConcurrentSkipListSet();
        for (int i = 0; i < 500; i++) {
            fixedThreadPool.submit(() -> {
                Singleton008 instance = Singleton008.getInstance();
                setObj.add(instance.hashCode());
            });
        }
        fixedThreadPool.shutdown();
        System.out.println("耗时:" + (System.currentTimeMillis() - start) + "ms");
        System.out.println("生成类数:" + setObj.size());
        for (Integer s : setObj) {
            System.out.println("hashcode:" + s);
        }
    }

 

输出

new Singleton008
耗时:72ms
生成类数:1
hashcode:346401817

 

 

 

 

002-创建型-03-单例模式(Singleton)【7种】

原文:https://www.cnblogs.com/bjlhx/p/11142097.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!