设计模式-02.单例模式

单例模式

学习途径来自菜鸟教程:https://www.runoob.com/design-pattern/singleton-pattern.html

单例模式(Singleton pattern),这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  • 属于创建型模式,它提供了一种创建对象的最佳方式。
  • 在任何情况下都只有一个实例。
  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

**主要解决:**一个全局使用的类频繁地创建与销毁。

**何时使用:**控制实例数目,节省系统资源的时候。

**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

**关键代码:**构造函数是私有的。

重 点

  1. 私有化构造器
  2. 了解线程对单例模式的影响,保证线程安全
  3. 延迟加载,避免类加载时就创建过多的无用单例从而造成内存浪费
  4. 防止序列化和反序列化破坏单例
  5. 防止反射对单例的破坏

优点

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点

没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

饿汉式单例

  • 写法1:
public class HungrySingleton{
    public static final HungrySingleton hungrySingleton = new HungrySingleton();
    
    //构造方法私有化
    private HungrySingleton(){}
    
    public static HungrySingleton getInstance(){return hungrySingleton;}
    
}
  • 写法2:
public class HungrySingleton{
    //加载顺序
    //先静态后动态
    //先上后下
    //先属性后方法
    public static final HungrySingleton hungrySingleton;
    
    static {
        hungrySingleton = new HungrySingleton()
    }
    
    //构造方法私有化
    private HungrySingleton(){}
    
    public static HungrySingleton getInstance(){return hungrySingleton;}
    
}

优点

  • 没有加锁,执行效率会提高。
  • 线程安全(基于 classloader 机制避免了多线程的同步问题)

缺点

  • 类加载时就初始化,(某些情况下)浪费内存。

懒汉式

1. 线程不安全

public class LazySimpleSingleton{
    public static LazySimpleSingleton instance;
    
    //构造方法私有化
    private LazySimpleSingleton(){}
    
    public static LazySimpleSingleton getInstance(){
        if(instance == null){
            //多线程情况下,可能有多个线程同时进入instance == null的条件,从而导致创建多个实例
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

优点

  1. 延时加载,节省空间

缺点

  1. 线程不安全

2. 线程安全

public class LazySimpleSingleton{
    public static LazySimpleSingleton instance;
    
    //构造方法私有化
    private LazySimpleSingleton(){}
    
    //加锁 syncronized 关键字
    //多线程下此处容易阻塞,其他线程在此处等待
    public synchronized static LazySimpleSingleton getInstance(){
        if(instance == null){
            instance = new LazySimpleSingleton();
        }
        return instance;
    }
}

优点

  1. 延时加载,节省空间
  2. 线程安全

缺点

  1. 效率低

3. 双检锁/双重校验锁(DCL)

双检锁/双重校验锁(DCL,即 double-checked locking)

public class LazyDoubleCheckSingleton{
    //volatile关键字,解决指令重排序问题
    public volatile static LazyDoubleCheckSingleton instance;
    
    //构造方法私有化
    private LazyDoubleCheckSingleton(){}
    
    public static LazyDoubleCheckSingleton getInstance(){
        //判断是否需要阻塞
        if(instance == null){
            //锁,多线程下可能会有部分线程进入此处
            synchronized(LazyDoubleCheckSingleton.class){
                //判断是否需要创建实例
                if(instance == null){
                    instance = new LazyDoubleCheckSingleton();
                }
       		}
        }
        return instance;
    }
}

注:jdk1.5起

优点

  1. 延时加载,节省空间
  2. 线程安全
  3. 效率高

缺点

  1. 实现较复杂
  2. 代码可读性较低

4. 登记式/静态内部类

/**
* 内部类加载方式及顺序:
*       ClassPath: LazyStaticInnerSingleton.class
*                  LazyStaticInnerSingleton.class$LazyHolder.class
* 优点:写法优雅,利用了java本身语法的特点,性能高,避免了内存浪费
* 缺点:
*/
public class LazyStaticInnerSingleton {
    //构造方法私有化
    private LazyStaticInnerSingleton(){
        //防止被反射破坏
        //if(LazyHolder.INSTANCE != null){
        //    throw new RuntimeException("不允许非法访问")
        //}
    }
    
    public static LazyStaticInnerSingleton getInstance(){
        //程序中调用getInstance时发现调用了LazyHolder内部类,
        //去加载LazyStaticInnerSingleton.class$LazyHolder.class
        //执行完LazyStaticInnerSingleton.class$LazyHolder.class的逻辑之后返回结果
        return LazyHolder.INSTANCE;
    }
    
    private static class LazyHolder {
        private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
    }
}

优点

  1. 延时加载,节省空间
  2. 线程安全

缺点

  1. 能够被反射破坏

5. 枚举式单例

枚举式单例是注册式单例的一种。

public enum EnumSingleton {
    INSTANCE;
    
    private  Object ojb;
    
    public Object getObj(){return obj;}
    
    public void setObj(Object obj){ this.ojb = obj;}

    public static EnumSingleton getInstance(){return INSTANCE;}
}

优点

  1. 线程安全

    同饿汉式类似,在声明时便初始化好,不存在线程问题

  2. 不被反射破坏

    public class ReflectTest{
        Class<?> clazz = LazyStaticInnerSingleton.class;
        //枚举类Enum没有无参的构造方法,通过访问构造方法获取实例
        Constructor c = clazz.getDeclaredConstructor(String.class, int.class);
        //构造方法为私有,设置“强吻”
        c.setAccessible(true);
        //此行会报错,Cannot reflectively create enum objects(不能通过反射创建枚举对象)
        Object instance1 = c.newInstance();
        System.out.println(instance1);
    }
    
    Construcor.java
        
    if(clazz.getModifiers() & Modifier.ENUM != 0)
     throw new IllegalArgumentException("Cannot reflectively create enum objects");
    

缺点

  1. 不是延时加载
  2. 不适合大批量创建单例的情况

6. 容器式单例

public class ContainerSingleton{
   
   private ContainerSingleton(){};
   
   public static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
   
   public static Object getInstance(String className){
       Object instance = null;
       if(!ioc.containsKey(className)) {
           synchronized(ioc){
               
               if(!ioc.containsKey(className)){
                   try{
                       instance = Class.forName(className).newInstance();
                       ioc.pub(className, instance);
                   }catch(Exception e){
                       e.printStackTrace();
                   }
                   return instance;
               }
        	}
       }else{
           //此处简单实现,这里还应该判断 ioc.get(className) == null
           //或者ioc.get(className) == new Object()的情况,
           //这两种情况下应该都需要重新ioc.pub(className, instance)
       	return ioc.get(className);   
       }
   }
}

反射破坏单例

public class ReflectTest{
    Class<?> clazz = LazyStaticInnerSingleton.class;
    //通过访问无参的构造方法获取实例
    Constructor c = clazz.getDeclaredConstructor(null);
    //构造方法为私有,设置“强吻”
    c.setAccessible(true);
    Object instance1 = c.newInstance();
    
    Object instance2 = c.newInstance();

    System.out.println(instance1);
    System.out.println(instance2);
    System.out.println(instance1 == instance2);
}

解决:可参考5.枚举式单例,或4.登记式单例代码中注释部分,对单例的私有方法进行判断改造

序列化破坏单例

public class SeriableSingleton implements Serializable {

    //序列化
    //把内存中对象的状态转换为字节码的形式
    //把字节码通过IO输出流,写到磁盘上
    //永久保存下来,持久化
   
    //反序列化
    //将持久化的字节码内容,通过IO输入流读到内存中来
    //转化成一个Java对象

    public  final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}

    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }

    //防止单例模式被序列化破坏,此处也用到了桥接模式
    private Object readResolve(){ return INSTANCE;}
     

ThreadLocalSingleton

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
            new ThreadLocal<ThreadLocalSingleton>(){
                @Override
                protected ThreadLocalSingleton initialValue() {
                    return new ThreadLocalSingleton();
                }
            };

    private ThreadLocalSingleton(){}

    public static ThreadLocalSingleton getInstance(){
        return threadLocaLInstance.get();
    }
}

经验之谈

  • 一般情况下,不建议使用第 1 种—(线程不安全)和第 2 种—(线程安全)懒汉方式。
  • 建议使用饿汉方式。
  • 只有在要明确实现 lazy loading 效果时,才会使用第 4 种–登记方式。
  • 如果涉及到反序列化创建对象时,可以尝试使用第 5 种—枚举方式。
  • 如果有其他特殊的需求,可以考虑使用第 3 种—双检锁方式。
posted @ 2020-05-03 19:32  嗨!阿克  阅读(117)  评论(0编辑  收藏  举报