单例模式

单例模式

单例模式的应用场景

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。单例模式是创建型模式。在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。

单例模式的常见写法
  • 1、饿汉式单例
    • 饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。
    • 优点:
      • 没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
      • 缺点:
        • 类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅 坑不拉屎。
    • Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例
    • 饿汉式单例写法:
      public class HungrySingleton {
          private final static    HungrySingleton hungrySingleton = new HungrySingleton();
      
          // 或者:
          //    private final static    HungrySingleton hungrySingleton ;
          //    static {
          //        hungrySingleton = new HungrySingleton();
          //    }
              private HungrySingleton(){
          
              }
          
              public static HungrySingleton getHungrySingleton() {
                  return hungrySingleton;
              }
          }
      
  • 2、懒汉式单例
    • 懒汉式单例的特点是:被外部类调用的时候内部类才会加载
    • 代码:
      public class LazySingleton {
      
         private static  LazySingleton lazySingleton =  null;
         private LazySingleton(){
      
         }
      
         public static LazySingleton getLazySingleton() {
             if(lazySingleton == null){
                 lazySingleton = new LazySingleton();
             }
             return lazySingleton;
         }
      }
      
      
      • 线程不安全,双重检查锁
      public class LazySingleton {
      
      private volatile  static  LazySingleton lazySingleton =  null;
      private LazySingleton(){
      
      }
      
      public static LazySingleton getLazySingleton() {
      
          if(lazySingleton == null){
              synchronized (LazySingleton.class){
                  if(lazySingleton == null){
                      lazySingleton = new LazySingleton();
                      //1.分配内存给这个对象
              //2.初始化对象
              //3.设置lazy指向刚分配的内存地址
              //CPU内存重排序,有可能2和3颠倒,解决:加volatile关键字
                  }
              }
          }
          return lazySingleton;
      }
      }
      
    • 内部类方式单例:
    //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
    //完美地屏蔽了这两个缺点
    public class LazyInnerClassSingleton {
        //默认使用LazyInnerClassGeneral的时候,会先初始化内部类
        //如果没使用的话,内部类是不加载的
        private LazyInnerClassSingleton() {
        }
        //每一个关键字都不是多余的
        //static 是为了使单例的空间共享
        //保证这个方法不会被重写,重载
        public static final LazyInnerClassSingleton getLazyInnerClassSingleton(){
            return LazyHolder.LAZY;
        }
        //默认不加载
        private static  class LazyHolder{
            private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
        }
    }
    
    • 静态内部类方式极端情况下单例会被破坏,如下代码
    public class LazyInnerClassSingletonTest {
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    
            Class<?> clazz = LazyInnerClassSingleton.class;
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            Object o1 = declaredConstructor.newInstance();
            Object o2 = declaredConstructor.newInstance();
    
    
            System.out.println(o1 == o2);//false
    
        }
    }
    
    • 可以在构造函数那里判断拦截一下
      private LazyInnerClassSingleton() { if(LazyHolder.LAZY != null){ throw new RuntimeException("不允许构建多个单例"); } }
    • 变为:
    //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
    //完美地屏蔽了这两个缺点
    public class LazyInnerClassSingleton {
        //默认使用LazyInnerClassGeneral的时候,会先初始化内部类
        //如果没使用的话,内部类是不加载的
        private LazyInnerClassSingleton() {
            if(LazyHolder.LAZY != null){
                throw new RuntimeException("不允许构建多个单例");
            }
        }
        //每一个关键字都不是多余的
        //static 是为了使单例的空间共享
        //保证这个方法不会被重写,重载
        public static final LazyInnerClassSingleton getLazyInnerClassSingleton(){
            return LazyHolder.LAZY;
        }
        //默认不加载
        private static  class LazyHolder{
            private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
        }
    }
    
    • 到此,还有更极端情况,序列化和反序列化方式也能破坏单例
    • 代码如下:
    public class SeriableSingleton implements Serializable{
    
        private static final SeriableSingleton seriableSingleton= new SeriableSingleton();
    
        private SeriableSingleton() {
        }
    
        public static SeriableSingleton getSeriableSingleton() {
            return seriableSingleton;
        }
    
        private String sayHello(){
            return "hello";
        }
    }
    
    //测试类:
    public class SeriableSingletonTest {
        public static void main(String[] args) {
            SeriableSingleton s1 = null;
            SeriableSingleton s2 = SeriableSingleton.getSeriableSingleton();
    
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream("SeriableSingleton.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(s2);
                oos.flush();
                oos.close();
    
    
                FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
                ObjectInputStream ois = new ObjectInputStream(fis);
                s1 = (SeriableSingleton)ois.readObject();
                ois.close();
    
                System.out.println(s1);
                System.out.println(s2);
                System.out.println(s1 == s2);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 解决办法:
      • 重写readResolve
      • 变为:
      import java.io.Serializable;
      //序列化就是说把内存中的状态通过转换成字节码的形式
      //从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO)
      //内存中状态给永久保存下来了
      
      //反序列化
      //讲已经持久化的字节码内容,转换为IO流
      //通过IO流的读取,进而将读取的内容转换为Java对象
      //在转换过程中会重新创建对象new
      public class SeriableSingleton implements Serializable{
      
          private static final SeriableSingleton seriableSingleton= new SeriableSingleton();
      
          private SeriableSingleton() {
          }
      
          public static SeriableSingleton getSeriableSingleton() {
              return seriableSingleton;
          }
      
      
          private Object readResolve(){
              return seriableSingleton;
          }
          private String sayHello(){
              return "hello";
          }
      }
      
  • 3、注册式单例
注册式单例有两种写法:一种为容器缓存,一种为枚举登记
  • 枚举式单例
  • 代码:
    public enum  EnumSingleton implements Serializable{
        INSTANCE;
        public static EnumSingleton getInstance(){
            return INSTANCE;
        }
        private  Object data;
    
        public Object getData() {
            return data;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
        private String sayHello(){
            return "hello Word";
        }
    }
    
    • 不重写readResolve情况下
    public class EnumSingletonTest {
        public static void main(String[] args) {
            EnumSingleton s1 = null;
            EnumSingleton s2 = EnumSingleton.getInstance();
    
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream("EnumSingleton.obj");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                oos.writeObject(s2);
                oos.flush();
                oos.close();
    
    
                FileInputStream fis = new FileInputStream("EnumSingleton.obj");
                ObjectInputStream ois = new ObjectInputStream(fis);
                s1 = (EnumSingleton)ois.readObject();
                ois.close();
    
                System.out.println(s1);
                System.out.println(s2);
                System.out.println(s1 == s2);
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    • 测试为true
    • 用jad反编译工具可以看到枚举登记单例的代码:
    public final class EnumSingleton extends Enum
    implements Serializable
    {
    
        public static EnumSingleton[] values()
        {
            return (EnumSingleton[])$VALUES.clone();
        }
    
        public static EnumSingleton valueOf(String name)
        {
            return (EnumSingleton)Enum.valueOf(com/exercise/eg/patterns/singleton/register/EnumSingleton, name);
        }
    
        private EnumSingleton(String s, int i)
        {
            super(s, i);
        }
    
        public static EnumSingleton getInstance()
        {
            return INSTANCE;
        }
    
        public Object getData()
        {
            return data;
        }
    
        public void setData(Object data)
        {
            this.data = data;
        }
    
        private String sayHello()
        {
            return "hello Word";
        }
    
        public static final EnumSingleton INSTANCE;
        private Object data;
        private static final EnumSingleton $VALUES[];
    
        static 
        {
            INSTANCE = new EnumSingleton("INSTANCE", 0);
            $VALUES = (new EnumSingleton[] {
                INSTANCE
            });
        }
    }
    
    发现枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对象。因此,枚举对 象不可能被类加载器加载多次次。那么反射是否能破坏枚举式单例呢
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<?> clazz = EnumSingleton.class;
        Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,Integer.class);
        constructor.setAccessible(true);
        constructor.newInstance("Jim",666);
    }
    
    运行结果:Cannot reflectively create enum objects
    • 看看 JDK 源码,进入 Constructor 的 newInstance()方法:
    public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); }@SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
    
    在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。
    • 枚举式单例也是《Effective Java》书中推荐的一种单例实现写法。在 JDK 枚举的语法特殊性,以及反射也为枚举保 驾护航,让枚举式单例成为一种比较优雅的实现
    • 注册式单例 容器缓存的写法
    //Spring中的做法,就是用这种注册式单例
    public class ContainerSingleton {
            private static Map<String,Object> iocMap = new ConcurrentHashMap<String,Object>();
        
            public static Object getInstance(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
                synchronized (iocMap){
                    if(!iocMap.containsKey(className)){
                        Class<?> aClass = Class.forName(className);
                        Object o = aClass.newInstance();
                        iocMap.put(className, o);
                        return o;
                    }
                    return iocMap.get(className);
                }
        
            }
        }
        //测试:
            public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
            Object object = ContainerSingleton.
                    getInstance("com.exercise.eg.patterns.singleton.register.Pojo");
            System.out.println("end");
        }
    
  • 4、ThreadLocal单例
    public class ThreadLocalSingleton {
    
        private static final ThreadLocal<ThreadLocalSingleton> singletonThreadLocal =
                new ThreadLocal<ThreadLocalSingleton>(){
                    @Override
                    protected ThreadLocalSingleton initialValue() {
                        return new ThreadLocalSingleton();
                    }
                };
    
        public static ThreadLocalSingleton getSingletonThreadLocal() {
            return singletonThreadLocal.get();
        }
    
        private ThreadLocalSingleton() {
        }
    }

ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值