设计模式---单例模式

4.单例模式(Singleton)

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

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

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

  (3)有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统就会出错,所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

  使用一个私有构造函数,一个私有静态变量以及一个公有静态函数来实现。

  私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

1.懒汉式-线程不安全

  以下实现中,私有静态变量uniqueInstance被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化uniqueInstance,从而节约资源。

 这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if (uniqueInstance == null) ,并且此时 uniqueInstance 为 null,那么会有多个线程执行 uniqueInstance = new Singleton(); 语句,这将导致实例化多次 uniqueInstance。

public class Singleton{
    /*持有私有静态变量,防止被引用,此处赋值为NULL,目的是为实现延迟加载*/
    private static Singleton uniqueInstance=null;
    /*私有构造方法,防止被实例化*/
    private Singleton(){
    }
    /*静态工程方法,创建实例*/
    public static Singleton getuniqueInstance(){
        if(uniqueInstance==null){
            uniqueInstance=new Singleton();
        }
        return uniqueInstance
    }
}

2.懒汉式-线程安全

  只需要对getuniqueInstance()方法加锁,那么在一个时间点只能有一个线程进入该方法,从而避免实例化多次uniqueInstance

  但是当一个线程进入该方法之后,其他试图进入该方法的线程都必须进行等待,即使uniqueInstance已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。

public class Singleton{
    private static Singleton uniqueInstance=null;
    private Singleton(){};
    public static synchronized Singleton getuniqueInstance(){//加锁
        if(uniqueInstance==null)
            uniqueInstance=new Singleton();
        return uniqueInstance;
    }
}

3.饿汉式-线程安全

线程不安全的原因是uniqueInstance被实例化多次,采取直接实例化uniqueInstance的方式就不会产生不安全的问题,但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。

private static Singleton uniqueInstance=new Singleton();

4.双重校验锁-线程安全

  uniqueInstance只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分代码进行,只有当uniqueInstance没有被实例化时,才需要进行加锁。

  双重校验锁先判断uniqueInstance是否已被实例化,如果没有被实例化,那么才对实例化语句进行加锁

public class Singleton{
    private static Singleton uniqueInstance;
    private Singleton(){};
    public static Singleton getuniqueInstance(){
        if(uniqueInstance==null){
            synchronized(Singleton.class){
                if(uniqueInstance==null){
                    uniqueInstance=new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

  考虑下面的实现,也就是只使用可一个if语句。在uniqueInstance==null的情况下,如果两个线程都执行了if语句,那么两个线程都会进入if语句块内。虽然if语句块内有加锁操作,但是两个线程都会执行uniqueInstance=new Singleton();这条语句,只是先后的问题,那么就会进行两次实例化。因此必须进行双重校验,也就是需要使用两个if语句。

if(uniqueInstance==null){
    synchronized(Singleton.class){
    uniqueInstacne=new Singleton();
    }
}

  uniqueInstance采用volatile关键字修饰也是很有必要的,uniqueInstance=new Singleton();这段代码其实是分三步执行:

  (1)为uniqueInstance分配内存空间

  (2)初始化uniqueInstance

  (3)将uniqueInstance指向分配的内存地址

  但是由于jvm指令重排的特性,执行的顺序可能变成1>3>2。指令重排在单线程的环境下不会出现问题,但是在多线程的环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行1和3,此时T2调用getuniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但是此时uniqueInstance还未被初始化。使用volatile可以禁止jvm指令重排,保证在多线程下也能正常运行

5.静态内部类实现

  单例模式使用内部类来维护单例的实现,jvm内部的机制能够保证一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,jvm能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。这种方法不仅具有延迟初始化的好处,而且jvm提供了对线程安全的支持。

public class Singleton{
    private Singleton(){};
    private static class SingletonHolder{
        private static final Singleton instance=new Singleton();
    }
    public static Singleton getuniqueInstance(){
        return SingletonHolder.instance;
    }
}
posted @ 2019-07-07 14:59  yjxyy  阅读(162)  评论(0编辑  收藏  举报