java单例模式的8种写法

单例模式(Singleton)是一种非常简单且容易理解的设计模式。顾名思义,单例即单一的实例,确切地讲就是指在某个系统中只存在一个实例,同时提供集中、统一的访问接口,以使系统行为保持协调一致

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:24  */ public class Test01 {     private static final Test01  INSTANCE=new Test01();      private  Test01(){      }      public static Test01 getINSTANCE() {         return INSTANCE;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test01.getINSTANCE().hashCode());             }).start();         }     } } 

私有的构造方法使得Test01完全被封闭起来 实例化工作是自己内部的事务

private static final 修饰 保证了 INSTANCE是私有的 ,不可见的不可访问的,static保证了静态性,在类被加载进内存时,就已经初始化 ,final保证INSTANCE是常量,是不能被修改的

外部只要调用公共的方法TEST01.getINSTANCE就可以获得唯一的实例对象了 

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:24  */ public class Test03 {     private static final Test03 INSTANCE;      static {         INSTANCE=new Test03();     }     private Test03(){      }      public static Test03 getINSTANCE() {         return INSTANCE;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test03.getINSTANCE().hashCode());             }).start();         }     } } 

此处将实例化操作放到静态代码块中

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:33  */ public class Test02 {     private static Test02 test02;     private Test02(){};      public static Test02 getInstance(){         if (test02==null){             test02=new Test02();         }          return test02;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test02.getInstance().hashCode());             }).start();         }     }  } 

恶汉模式如果没人使用,但是却实例化对象 ,这样一块内存区不是白浪费了 这样单杀了懒汉模式的写法

只有当某一个线程第一次调用getINSTANCE时才会进行实例化操作 之后再有线程访问直接返回对象

这样程序乍看确实没什么问题 但是在多线程环境下 可能会有多个线程进入到了getINSTANCE方法内,这样就会导致原来已经实例化的对象被覆盖掉

为了保证线程安全 我们给getINSTANCE方法加上 synchronized同步锁 下面看第四种写法

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:33  */ public class Test04 {     private static Test04 test02;     private Test04(){};      public static synchronized Test04 getInstance(){         if (test02==null){             test02=new Test04();         }          return test02;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test04.getInstance().hashCode());             }).start();         }     }  } 

这样确实没有什么问题 然而这样的做法是要付出一定代价的,试想,线程还没进入方法内部便不管三七二十一直接加锁排队,会造成线程阻塞,资源与时间被白白浪费。我们只是为了实例化一个单例对象而已,犯不上如此兴师动众,使用synchronized让所有请求排队等候。所以,要保证多线程并发下逻辑的正确性,同步锁一定要加得恰到好处

下面看第五种写法 在方法体内部加锁:

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:33  */ public class Test05 {     private static Test05 test02;     private Test05(){};      public static  Test05 getInstance(){         if (test02==null){             synchronized (Test05.class){                 test02=new Test05();             }                      }          return test02;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test05.getInstance().hashCode());             }).start();         }     }  } 

这样在多线程环境也会有一定问题 ,可能会有多个线程同时通过了 tese02==null 的判断进入了方法里,这样也会造成重复的实例化

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:33  */ public class Test06 {     private static  volatile  Test06 test02;     private Test06(){};      public static Test06 getInstance(){         if (test02==null){             synchronized (Test06.class){                 if (test02==null){                     test02=new Test06();                 }             }         }          return test02;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test06.getInstance().hashCode());             }).start();         }     }  } 

我们一共用了2个嵌套的判空逻辑,这就是懒加载模式的“双检锁”:外层放宽入口,保证线程并发的高效性;内层加锁同步,保证实例化的单次运行。如此里应外合,不仅达到了单例模式的效果,还完美地保证了构建过程的运行效率,一举两得。

package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:51  */ public class Test07 {      private Test07(){};      private static class Test0701{         private static final  Test07 test07=new Test07();     }          public   static  Test07 getInstance(){         return Test0701.test07;     }      public static void main(String[] args) {         for (int i=0;i<1000;i++){             new Thread(()->{                 System.out.println(Test07.getInstance().hashCode());             }).start();         }     }      } 
package com.cyc.mystudy.singleton;  /**  * @Author cyc  * @create 2022/7/30 11:57  */ public enum Test08 {      INSTANCE;      public  void m(){         System.out.println("业务代码");     }      public static void main(String[] args) {         Test08.INSTANCE.m();     } } 

在一般情况下我们使用饿汉模式,恶汉模式不用担心多线程环境会出问题,写法上也比较简单,

我们不用为了省一点性能而去给自己造成麻烦