单例设计模式

Posted cjm09

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例设计模式相关的知识,希望对你有一定的参考价值。

单例模式是Java中常见的一种设计模式,单例模式的写法有好几种,这里主要介绍饿汉式和懒汉式以及懒汉式的改进型。

单例设计模式确保一个类只有一个实例对象,且向所有其他对象提供这一实例。

单利模式的实现步骤:

1、私有化构造函数,避免其他类可以直接创建单例类的对象;

2、在本类中创建唯一的实例对象,使用private static修饰;

3、对外提供一个公开的静态方法,供其他类获取单例类的唯一实例。

饿汉式单例:

 class Single{
      // 私有化构造函数
      private Single(){}

      // 创建唯一实例
      private static Single single= new Single();

      // 提供一个公开的获取方法
      public static Single getSingle() {
         return single;
      }
 }

饿汉式单例在类加载的时候,创建对象,因此类加载的速度慢,但线程安全。

懒汉式单例:

 class Single{
      // 私有化构造函数
      private Single(){}

      // 创建唯一实例
      private static Single single= null;

      // 提供一个公开的获取方法,在第一次调用的时候创建对象实例
      public static Single getSingle() {
         if(single == null) {
            single = new Single(); //创建实例
         }
         return single;
      }
 }

懒汉式单例类加载时不会创建对象,调用时才会创建对象,因此类加载速度快,但线程不安全,一般要配合synchronized使用。

懒汉式单例模式优化版(同步方法方式):

class Single{
    // 声明一个私有的静态变量
    private static Single single=null;
	
    // 构造器私有化,避免外部直接创建对象
    private Single(){}
	
    // 创建一个对外的公共的静态方法访问变量,如果变量没有对象,创建该对象
    // 任何线程来的时候都需要等待,等上一个执行完了以后才能进去
    public synchronized static Single getSingle(){   
	if(null==single){
	   single=new Single();	
	}
        return single;
    }	
}

synchronized关键字解决了多线程环境下的懒汉式单例模式的线程安全问题,但同时也有个小小的缺憾,就是synchronized关键字开销比较大。可以使用双重检测机制进行优化。

懒汉式单例模式优化版(双重检测机制):

class Single{
    //声明一个私有的静态变量
    private static Single single=null;
	
    //构造器私有化,避免外部直接创建对象
    private Single(){}
	
    public static Single getSingle(){
       // 这样判断两次,可以提高效率   因为a、b进来之后,a执行完,b进入的时候如果single不为空的话直接返回获取对象
       // 提高已存在对象的访问效率
       if (null==single) {
          //同步块,因为静态方法中没有this,所以只能锁它的字节码信息
          synchronized (Single.class) {
	      if(null==single){
		  single=new Single();
	      }
	  }
       }
       return single;
    }	
}

 双重检测机制的第一次检测 if (null==single) 是为了提高代码的执行效率,如果已创建了实例,再次调用getSingle()方法就不必进入同步代码块,不用竞争锁,直接返回已创建的实例;

第二次检测  if (null==single) 可以防止二次创建实例。所以两次检测都必不可少。

双重检测机制理论上是完美的,但实际上仍然有问题,主要原因就是

      single=new Single();

这个语句是非原子操作,可以分为三个步骤:

1、为single分配内存;

2、调用构造函数初始化single;

3、将single对象指向分配的内存空间(此时single才不为null);

由于JVM具有指令重排的特性,执行顺序可以是 1 - 2 - 3,也可以是1 - 3 - 2。指令重排在单线程情况下不会出现问题,但多线程情况下会导致一个线程获取到一个未初始化的实例。例如:线程T1执行了1和3,此时T2调用getSingle()方法后发现single不为null,因此返回single,但此时single还没有被初始化。

 要解决这个问题需要使用volatile关键字。

           private static volatile Single single=null;

volatile关键字会禁止JVM指令重排,从而保证在多线程下正常执行。

以上是关于单例设计模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

从 Viewpager2 片段访问父片段函数

你熟悉的设计模式都有哪些?写出单例模式的实现代码