设计模式之单例模式

一、什么是单例模式

单例模式简单来说,就是要保证一个类在进程内有且只有一个实例对象。这个类对外提供了唯一获取其实例对象的静态方法,且构造方法私有化。

二、单例模式实现方式

  • 懒汉式(4种)
  • 饿汉式(2种)
  • 静态内部类(1种)
  • 枚举(1种)

三、懒汉式

1、方式一

public class SingletonDemo3 {

     //直接初始化对象
     private static SingletonDemo3 instance;

     //构造方法私有化
     private SingletonDemo3(){}

     //
     public static SingletonDemo3 getInstance( ){
         if(instance==null){
             instance = new SingletonDemo3();
         }
         return instance;
     }
 }

缺点:并发场景时,线程不安全。

结论:不推荐使用。

2、方式二

针对实现方式一来进行升级,加入线程同步锁synchronized,保证线程安全。

public class SingletonDemo4 {

     //直接初始化对象
     private static SingletonDemo4 instance;

     //构造方法私有化
     private SingletonDemo4(){}

     //
     public static synchronized SingletonDemo4 getInstance( ){
         if(instance==null){
             instance = new SingletonDemo4();
         }
         return instance;
     }
 }

缺点:由于synchronized加在了方法上,并发场景下都要获取锁,性能较差。

结论:不推荐使用。

3、方式三(双重检查+同步锁)

针对实现方式二进行升级,将synchronized放在方法代码块中,保证并发性能。

public class SingletonDemo5 {

     //直接初始化对象
     private static SingletonDemo5 instance;

     //构造方法私有化
     private SingletonDemo5(){}

     //
     public static SingletonDemo5 getInstance( ){
         if(instance==null){
             synchronized (SingletonDemo5.class){
                 instance = new SingletonDemo5();
             }
         }
         return instance;
     }
 }

缺点:由于java 虚拟机会对class进行指令重排序(JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能),在JVM中初始化对象分为三步:

  1. 为初始化对象分配内存空间;
  2. 初始化实例对象;
  3. 将初始化对象指定分配好的内存空间。

在发生指令重排序后,执行顺序为:1->3->2 。并发场景下,可能线程1创建单例时执行了1->3,线程2发现单例已创建,其实得到的是未初始化完成的对象,导致NPE异常。

结论:不推荐使用。

4、方式四(双重检查+同步锁+volatile)

针对实现方式三存在的问题进行升级,在单例对象变量前加入volatile关键字修饰,防止指令重排序。

public class SingletonDemo6 {

     //直接初始化对象
     private static volatile SingletonDemo6 instance;

     //构造方法私有化
     private SingletonDemo6(){}

     //
     public static SingletonDemo6 getInstance( ){
         if(instance==null){
             synchronized (SingletonDemo6.class){
                 if(instance == null){
                     instance = new SingletonDemo6();
                 }
             }
         }
         return instance;
     }
 }

结论:推荐使用。

四、饿汉式

饿汉式,简单来说,在JVM加载类的时候就已经创建了对象,典型的以空间换时间。

方式一:

public class SingletonDemo1 {

     //直接初始化对象
     private static SingletonDemo1 instance = new SingletonDemo1();

     //构造方法私有化
     private SingletonDemo1(){}

     //
     public static SingletonDemo1 getInstance( ){
         return instance;
     }
 
 }

方式二:

public class SingletonDemo2 {

     //定义初始化对象
     private static SingletonDemo2 instance;

     //构造方法私有化
     private SingletonDemo2(){}

     // 静态代码块初始化
     static {
         instance = new SingletonDemo2();
     }
     
     public static SingletonDemo2 getInstance( ){
         return instance;
     }
 }

优点:效率高,线程安全。

缺点:若是单例对象过多,比较浪费内存。

结论:不推荐使用

五、静态内部类

public class SingletonDemo7 {

     //构造方法私有化
     private SingletonDemo7(){
     }

     //
     public static SingletonDemo7 getInstance( ){
         return SingletonInner.instance;
     }
 
     /**
      * 静态内部类,真正调用时才初始化单例对象
      */     private static class SingletonInner{
         private static SingletonDemo7 instance = new SingletonDemo7();
     }
 }

优点:延迟加载,在真正调用时进行初始化对象;线程安全;效率高(不需要同步锁)

结论:推荐使用。

六、枚举

public enum SingletonDemo8 {

     // 定义一个枚举实例,就代表了该类的单例对象
     INSTANCE;
 }

优点:线程安全,效率高

结论:推荐使用。

发表评论