zoukankan      html  css  js  c++  java
  • 初识设计模式(单例模式)

    前言:继续学习设计模式。单例模式的类图好像是最简单的呢。转载请注明出处:http://www.cnblogs.com/yuxiaole/p/9310345.html

    单例模式(Singleton pattern)

    定义:确保一个类只有一个示例,并提供一个全局访问点。

    类图:

    使用场景:有一些对象其实我们只需要一个。比如:线程池、缓存、日志对象等,这些对象如果有多个,会导致许多问题产生。

    总结:

      1、单例模式给了我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。因为单例模式使用了延迟示例化的方式创建对象。

    (全局变量的缺点:如果将对象赋值给一个全局变量,那么我们必须在程序一开始就创建好对象,但是万一这个对象很耗费资源,而且程序在这次的执行过程中又一直没有用到它,这就会形成浪费。而且全局变量也不能保证只有一个实例)。

      2、没有公开的构造器。构造器是私有的。

      3、现在的java版本,垃圾收集器不会回收单例模式创建的对象。

      4、如果程序有两个类加载器,同时又使用了单例模式,这种情况下不同的类加载器可能都会去加载这个单例类。解决方法:自行指定类加载器,并指定同一个类加载器。

    demo:

      1、如果是单线程,代码可以如下:

    /**
     * 单线程中的单例实现
     * Created by yule on 2018/7/14 17:04.
     */
    public class Singleton1 {
        private static Singleton1 uniqueInstance;
        
        private Singleton1(){
            
        }
        
        public static Singleton1 getInstance(){
            if(uniqueInstance == null){
                uniqueInstance = new Singleton1();
            }
            return uniqueInstance;
        }
    }

      这种的缺点是不适用与多线程中。 

      2、同步 getInstance() 方法:如果是多线程中,不关心性能问题,这是最简单有效的方式,则代码可以如下(加了synchronized):

    public class Singleton2 {
        private static Singleton2 uniqueInstance;
    
        private Singleton2(){
        }
    
        public static synchronized Singleton2 getInstance(){
            if(uniqueInstance == null){
                uniqueInstance = new Singleton2();
            }
            return uniqueInstance;
        }
    }

      这种的缺点在于:同步的 getInstance() 的做法会拖垮性能,可能使执行效率下降100倍。毕竟只有第一次执行此方法时,才真正需要同步。换句话说,一旦设置好 uniqueInstance 变量,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘

      3、急切实例化(饿汉模式):保证了线程安全,如果应用程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,可以采用下面这种方式:

    public class Singleton3 {
        private static Singleton3 uniqueInstance = new Singleton3();
    
        private Singleton3(){
        }
    
        public static Singleton3 getInstance(){
            return uniqueInstance;
        }
    }

      这种方式是在静态初始化器(static initializer)中创建单例。这也是保证了线程安全。利用这个做法,我们依赖 JVM 在加载这个类时马上创建此唯一的单例实例。JVM 保证在任何线程访问 uniqueInstance 静态变量之前,一定先创建此实例。

      这种方式的缺点在于:初始化时如果很耗费资源,而且程序在这次的执行过程中又一直没有用到它,这就会形成浪费。

      4、双重检查加锁:如果性能是关心的重点,则这种方式可以减少时间的耗费,减少 getInstance() 中使用同步的方式(注意加 volatile):

    public class Singleton4 {
        private volatile static Singleton4 uniqueInstance = null;
    
        private Singleton4(){
        }
    
        public static Singleton4 getInstance(){
            if(uniqueInstance == null){
                synchronized (Singleton4.class){
                    if(uniqueInstance == null){
                        uniqueInstance = new Singleton4();
                    }
                }
            }
            return uniqueInstance;
        }
    }

      这种方式,进入同步区块只会在第一次才进入。

      这里的 singleton 使用double check locking是需要加volatile的(前提是jdk1.5之后);

      如果不加volatile,虽然里面加了锁,能保证这个同步块至多只有一个线程运行,但是不能保证有序性,即在此句中: instance = new Singleton(); 这不是一个原子操作,所以构造方法是有可能没有执行完,instance就拿到了这个值,从而出现问题。也就是说:这一块代码,有线程A和线程B,如果A先进入第一个if语句,A线程开始实例化Singleton对象,对象的实例化有三个步骤: 分配内存空间,初始化对象,将内存中的地址赋值给变量。这里可能会发生重排序,如果这里先执行分配和赋值操作,线程B此时进入第一个if语句,发现变量不为null,直接返回这个实例,然后B线程此时拿到的可能是还没有实例化的对象。 所以用volatile修饰singleton就可以避免这种重排序了。 注意:volatile不能保证操作的原子性。

      在getInstance上加同步,不需要考虑这个,是由于 synchronized 能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

      至于为何需要这么写请参考:Java并发编程:volatile关键字解析、《Java 中的双重检查(Double-Check)》http://blog.csdn.net/dl88250/article/details/5439024 和 http://www.iteye.com/topic/652440 

    转载请注明出处:http://www.cnblogs.com/yuxiaole/p/9310345.html 

  • 相关阅读:
    Count on a Tree II
    DZY Loves Math
    二次剩余
    exCRT & 骆克强乘法
    CF 585 E Present for Vitalik the Philatelist
    Dirichlet 前缀和的几种版本
    51nod 1630(定积分 + 期望)
    Atcoder刷题小记
    3194. 【HNOI模拟题】化学(无标号无根树计数)
    3754. 【NOI2014】魔法森林(LCT)
  • 原文地址:https://www.cnblogs.com/yuxiaole/p/9310345.html
Copyright © 2011-2022 走看看