Java设计模式(一)—— 单例模式

1、单例模式

确保一个类只有一个实例,并提供该实例的全局访问点。

1.1 饿汉式-线程安全

 public class Hungry {
     private static Hungry hungry = new Hungry();
     
     private Hungry(){
         
     }
     public static Hungry getInstance(){
         return hungry;
     }
 }
  • 会造成资源的浪费

1.2 懒汉式-线程不安全

 public class LazyMan {
     private static LazyMan lazyMan;
     private LazyMan(){}
     public static LazyMan getInstance(){
         if(lazyMan == null){
             return lazyMan = new LazyMan();
         }
         return lazyMan;
     }
 }
  • 对象延迟实例化,没有用到该类就不会实例化该对象,从而节约资源;
  • 这个实现是线程不安全的,如果多个线程同时进入if(lazyMan) == null,并且此时 lazyMan 为 null,那么会有多个线程执行return new LazyMan()语句,这将导致实例化多次lazyMan

1.3 双重检验锁(DCL)-线程安全

 public class LazyMan_Lock {
     private volatile static LazyMan_Lock lazyMan_lock ;
     private LazyMan_Lock(){}
     public static LazyMan_Lock getInstance(){
         if(lazyMan_lock == null){
             synchronized (LazyMan_Lock.class){
                 if(lazyMan_lock == null){
                     return lazyMan_lock = new LazyMan_Lock();
                 }
             }
         }
         return lazyMan_lock;
     }
 }

如果只存在一个 if 判断

     if(lazyMan_lock == null){
         synchronized (LazyMan_Lock.class){
             return lazyMan_lock = new LazyMan_Lock();
         }
     }
  • 如果只用一个if语句判断,在lazyMan_lock == null的情况下,如果两个线程同时进入了if语句,那么两个线程都会进入if语句块内。虽然if语句块内有加锁操作,但是两个线程都会执行lazyMan_lock = new LazyMan_Lock();这条语句,只是先后问题,那么就会进行两次实例化。因此必须使用双重检验锁,也就是需要使用两个if语句:第一个if语句用来避免lazyMan_lock未被实例化之后的加锁操作;第二个if语句进行了加锁,所以只能有一个线程进入,就不会出现lazyMan_lock == null时两个线程同时进行实例化操作;

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

    • lazyMan_lock分配内存空间;
    • 初始化lazyMan_lock
    • lazyMan_lock指向分配的内存地址;

但是由于JVM具有指令重排的特性,执行顺序有可能变成 1-3-2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行了1和3,此时T2调用getInstance()后发现lazyMan_lock不为空,因此返回lazyMan_lock,但此时lazyMan_lock还未被初始化。

使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。

1.4 静态内部类实现单例

 public class Holder {
     public Holder(){}
     public static Holder getInstance(){
         return InnerClass.HOLDER;
     }
     private static class InnerClass{
         private static final Holder HOLDER = new Holder();
     }
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
         Holder holder = Holder.getInstance();
         Class clazz = Holder.class;
         Constructor<Holder> constructor = (Constructor<Holder>) clazz.getDeclaredConstructor(null);
         constructor.setAccessible(true);
         Holder holder1 = (Holder) constructor.newInstance();
         System.out.println(holder == holder1);
     }
 }
  • Holder类被加载时,静态内部类InnerClass没有被加载进内部,只有当调用getInstance()方法从而触发InnerClass.HOLDERInnerClass才会被加载,初始化Holder实例,并且 JVM 能确保 HOLDER 只被实例化一次;
  • 这种方法不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

1.5 枚举实现单例

 public enum EnumSingle {
     INSTANCE;
     public EnumSingle getInstance(){
         return INSTANCE;
     }
 }class EnumSingleTest{
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
         EnumSingle instance1 = EnumSingle.INSTANCE;Class clazz = EnumSingle.class;
         Constructor<EnumSingle> c = clazz.getDeclaredConstructor(String.class,int.class);
         c.setAccessible(true);
         EnumSingle instance2 = c.newInstance();System.out.println(instance1);
         System.out.println(instance2);
     }
 }
 ========抛出异常
     Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
     at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
     at com.xiaojian.single.EnumSingleTest.main(EnumSingle.java:25)
  • 可以防止反射破坏单例,使用反射获取实例会抛出异常:Cannot reflectively create enum objects。详情请看反射构建实例的方法newInstance()源码

    Java设计模式(一)—— 单例模式_第1张图片

你可能感兴趣的:(设计模式,java基础,java,设计模式,单例模式,架构)