美文网首页
Java——单例模式

Java——单例模式

作者: 英勇青铜5 | 来源:发表于2017-04-26 23:37 被阅读207次

    学习资料:

    《Java程序性能优化》,这本书蛮不错的,豆瓣评分挺高7.9。本篇就是第2章第一章节的读书笔记

    最近项目中经常用到单例模式,虽然能手写出来,但了解的东西并不多,并不确定为何要这样写,以及这样写的好处,书上正好看到,就学习了解


    1. 单例模式

    总体来说设计模式分为三大类:<p>
    创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。<p>
    结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。<p>
    行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

    摘自Java 的 23 种设计模式全解析


    单例模式是一种对象创建模式, 可以用来确保一个类只产生一个对象的具体实例。

    适用场景:

    • 系统的关键组件
    • 被频繁使用的对象

    好处:

    1. 对于频繁使用的对象,可以省略创建对象所花费的时间。尤其是一些重量级的对象,可以省下一些系统开销
    2. 由于new次数减少,对系统内存的使用频率也会降低,从而减轻GC压力,缩短GC停顿时间

    1.1 简单实例

    单例模式的核心在于通过一个方法返回唯一的对象实例

    代码

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton is created");// 创建单例的过程可能会比较慢
        }
    
        private static Singleton instance = new Singleton();
    
        public Singleton getInstance() {
            return instance;
        }
    }
    

    代码的核心在于:

    一个private访问级的构造函数;private staticinstance成员变量;public staticgetInstance()方法

    这种写法简单可靠,缺点就是 无法对instance实例进行延迟加载


    1.1.1 缺点

    当采用简单实例形式时,若此时的单例类还扮演着其他的角色,由于instance成员是static的,当JVM加载单例类时,单例对象就会被创建,导致在任何时候想要试用这个单例类,都会进行初始化这个单例变量

    public class Singleton {
        private Singleton() {
            System.out.println("Singleton is created");// 创建单例的过程可能会比较慢
        }
    
        private static Singleton instance = new Singleton();
    
        public static Singleton getInstance() {
            return instance;
        }
    
        public static void createString(){ //模拟单例类扮演其他角色
            System.out.println("CreateString in Singleton");
        }
    }
    

    使用Singleton.createString(),输出结果:

    Singleton is created
    CreateString in Singleton
    

    当使用Singleton.createString()方法时,总会先创建出一个Singleton对象实例,这就是所谓的不满足实例延迟加载


    1.2 实例延迟加载

    代码:

    public class LazySingleton {
        private LazySingleton() {
            System.out.println("LazySingleton is created");
        }
    
        private static LazySingleton instance = null;
    
        public static synchronized LazySingleton getInstance() {
            if (null == instance) {
                 instance = new LazySingleton();
            }
            return instance;
        }
    }
    

    首先,将静态成员变量的初始值赋予null,确保启动时没有额外的负载

    其次,在getInstance()方法中,判断当前的单例是否已存在,若存在则返回;不存在则建立单例实例

    需要注意的是在getInstance()前加了synchronized,不加的话当在多线程时,线程1正在创建单例过程中,完成赋值前,线程2可能判断instancenull,线程2就会启动创建单例的语句,导致多个实例被创建

    这种延迟加载的缺点在于性能消耗


    1.2.1 验证synchronized作用

    getInstance()前的synchronized去掉,修改代码

        public static  LazySingleton getInstance() {
            if (null == instance) {
                try {
                    TimeUnit.MILLISECONDS.sleep(10);//模拟耗时操作,线程sleep
                    instance = new LazySingleton();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
            return instance;
        }
    

    测试:

    public class MyThread extends Thread {
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                LazySingleton instance = LazySingleton.getInstance();
                System.out.println(instance);
            }
        }
    
        public static void main(String[] args) {
            MyThread[] myThreads = new MyThread[5];
            for (int i = 0; i < 5; i++) {
                myThreads[i] = new MyThread();
            }
    
            beginTime = System.currentTimeMillis();
            for (MyThread myThread : myThreads) {
                myThread.start();
            }
        }
    }
    

    部分输出结果:

    LazySingleton is created
    l.single.LazySingleton@71053f05
    l.single.LazySingleton@71053f05
    l.single.LazySingleton@71053f05
    LazySingleton is created
    l.single.LazySingleton@6c749686
    l.single.LazySingleton@6c749686
    l.single.LazySingleton@6c749686
    LazySingleton is created
    l.single.LazySingleton@3a9ea3d2
    l.single.LazySingleton@3a9ea3d2
    l.single.LazySingleton@3a9ea3d2
    

    由结果看出,LazySingleton被多次创建,说明synchronized同步后可以避免实例被多次创建

    在其他的博客看到,还用volatile来修饰instance

    private volatile static LazySingleton instance = null
    

    暂时对volatile不了解,以后再学习


    1.2.2 延迟加载带来的性能消耗

    使用了synchronizedLazySingleton在多线程下会有更多的性能消耗

    测试:

        @Override
        public void run() {
            long  beginTime = System.currentTimeMillis();
            for (int i = 0; i < 1000000; i++) {
    //            Singleton.getInstance();
                LazySingleton.getInstance();
            }
            System.out.println("spend: " + (System.currentTimeMillis() - beginTime));
        }
    

    当使用Singleton.getInstance()平均耗时25 milliseconds

    使用LazySingleton.getInstance()平均耗时200 milliseconds

    实际耗时需要根据自己电脑来确定


    1.3 改进型延迟加载

    上面的LazySingleton虽然支持了延迟加载并防止了多次创建实例,却导致了额外的性能消耗,推荐两种改进形式:双检查锁与内部类

    两种方式在使用时根据场景选择,若需要通过构造方法传递参数,则选择双检查锁形式;若不需要则都可以

    1.3.1 双检查锁形式

    public class LazySingleton {
        private LazySingleton() {
            System.out.println("LazySingleton is created");
        }
    
        private static volatile LazySingleton instance = null;
    
        
        public static LazySingleton getInstance() {
            if (null == instance) {
                synchronized (LazySingleton.class) {
                    if (null == instance) {
                        instance = new LazySingleton();
                    }
                }
            }
            return instance;
        }
    }
    

    getInstance()方法内,先对instance进行判断,若不为null则不需要进行同步锁操作,从而避免了同步锁在多线程下带来的性能消耗,而且由于有同步锁,也能避免多次创建实例

    经测试,这种方式使用1.2.2的方式测试,平均耗时15 milliseconds
    1.2.1的方式测试,也没有出现多处创建实例


    1.3.1 内部类方式

    代码:

    public class InclassSingleton {
        private InclassSingleton() {
            System.out.println("InclassSingleton is created");
        }
    
        private static class SingletonHolder {
            private static InclassSingleton instance = new InclassSingleton();
        }
    
        public static InclassSingleton getInstance() {
            return SingletonHolder.instance;
        }
    }
    

    InclassSingletong被加载时,内部类并不会加载,而当getInstance()调用时,才会初始化instance

    由于实例的建立是在类加载时完成,天生多线程友好,所有不需要同步synchronized关键字

    这种两种形式基本可以保证不会创建多个实例,只有极特殊的情景,例如通过反射强行调用单例类的私有构造函数会造成多次创建实例


    1.4 单例模式的序列化

    public class Singleton implements Serializable {
        private Singleton() {
            System.out.println("Singleton is created");// 创建单例的过程可能会比较慢
        }
    
        private static Singleton instance = new Singleton();
    
        public static Singleton getInstance() {
            return instance;
        }
    
        public static void createString() { //模拟单例类扮演其他角色
            System.out.println("CreateString in Singleton");
        }
    
    
        protected Object readResolve()  {
            return instance;
        }
    }
    

    进行单元测试:

        @Test
        public void test() throws Exception {
            Singleton s1 = null;
            Singleton s0 = Singleton.getInstance();
    
            String fileName ="Singleton.txt";
            //将实例串行化到文件
            FileOutputStream fos = new FileOutputStream(fileName);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s0);
            oos.flush();
            oos.close();
    
            //从文件读出所有的单例类
            FileInputStream fis = new FileInputStream(fileName);
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (Singleton) ois.readObject();
    
            //进行比较
            assertEquals(s0,s1);
        }
    

    若将readResolve()方法去掉,则会报异常,说s1s0指向不同的实例

    需要序列化单例类的场景很少见,这里了解下


    2. 最后

    学习了解常见的设计模式,以后阅读一些库的源码或者自己写代码时也有些帮助

    共勉 :)

    相关文章

      网友评论

          本文标题:Java——单例模式

          本文链接:https://www.haomeiwen.com/subject/sbawzttx.html