初识设计模式(单例模式)

前言:继续学习设计模式。单例模式的类图好像是最简单的呢。转载请注明出处: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 

原文地址:https://www.cnblogs.com/yuxiaole/p/9310345.html