石一歌的单例模式笔记

单例模式

单例模式只能有一个实例。

单例类必须创建自己的唯一实例。

单例类必须向其他对象提供这一实例。

饿汉式

  • 静态常量

    public class HungryMan {
        // private 私有化构造方法,无法被外部实例化
        private HungryMan() {
        }
    	// private 自行实例化,无法被直接调用
        // static 静态方法只能调用静态成员
        private final static HungryMan HUNGRY = new HungryMan();
    	// 暴露调用方法
        // static 外部无法实例化,调用方法必须使用static修饰
        public static HungryMan getInstance() {
            return HUNGRY;
        }
    }
    
    • 优点
      • 简单,线程安全
    • 缺点
      • 在类装载的时候就完成实例化,容易浪费内存
  • 静态代码块

    仅仅将自行实例化的操作放在了静态代码块中,与上一种无实际区别。

    public class HungryManTwo {
        private HungryManTwo() {
        }
    
        private final static HungryManTwo HUNGRY;
    
        static {
            HUNGRY = new HungryManTwo();
        }
    
        public static HungryManTwo getInstance() {
            return HUNGRY;
        }
    
    }
    

懒汉式

  • 一代目

    针对饿汉式的问题,将实例化的过程留到调用方法时,减少内存使用。

    public class LazyMan {
        private LazyMan() {
        }
    
        private static LazyMan lazyMan;
    
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }
    
    • 优点
      • 懒加载机制,避免浪费内存
    • 缺点
      • 线程不安全
  • 二代目(同步方法)

    针对上一代的问题,使用synchronized同步方法,保证线程安全。

    public class LazyManTwo {
        private LazyManTwo() {
        }
    
        private static LazyManTwo lazyMan;
    
        public synchronized static LazyManTwo getInstance() {
            if (lazyMan == null) {
                lazyMan = new LazyManTwo();
            }
            return lazyMan;
        }
    }
    
    • 优点
      • 懒加载机制,避免浪费内存;线程安全
    • 缺点
      • 效率低
  • 三代目(同步代码块)

    针对上一代的问题,将同步方法换为同步代码块,提高效率。

    public class LazyManThree {
        private LazyManThree() {
        }
    
        private static LazyManThree lazyMan;
    
        public static LazyManThree getInstance() {
            if (lazyMan == null) {
            	synchronized (LazyManThree.class) {
                	lazyMan = new LazyManThree();
                }
            }
            return lazyMan;
        }
    }
    
    • 优点
      • 懒加载机制,避免浪费内存;效率高
    • 缺点
      • 线程不安全
  • 四代目(双重检查)

    针对上两代的问题,使用DLC双重校验加锁,在提高效率的同时保证线程安全。

    public class LazyManFour {
        private LazyManFour() {
        }
    
        private volatile static LazyManFour lazyMan;
    
        public static LazyManTwo getInstance() {
            if (lazyMan == null) {//提高效率
                synchronized (LazyManFour.class) {
                    if (lazyMan == null) {//保证线程安全
                        lazyMan = new LazyManFour();
                    }
                        /*
                        初始化操作并非原子命令,可能指令重排导致npe异常
                        分配内存空间
                        初始化对象
                        指向分配好的内存空间
                        用volatile 解决指令重排
                         */
                }
            }
            return lazyMan;
        }
    }
    
    • 优点
      • 懒加载机制,避免浪费内存;效率高;线程安全

其他方式

  • 静态内部类

    public class Holder {
        private Holder() {
        }
    
        public static Holder getInstance() {
            return InnerClass.HOLDER;
        }
    
        public static class InnerClass {
            private static final Holder HOLDER = new Holder();
            /*
            只会在外部类调用内部类时,随内部类初始化加载一次,线程安全
             */
        }
    }
    
    • 优点
      • 懒加载机制,避免浪费内存;效率高;线程安全
  • 枚举类

    public enum EnumSingle {
        INSTANCE;
    
        public EnumSingle getInstance() {
            return INSTANCE;
        }
        /*
        注意,直接查看反编译的文件的无参构造是错误的,jad反编译文件可知,enum是有参构造
        枚举单例无法被反射破坏
        */
    }
    
    • 优点
      • 懒加载机制,避免浪费内存;效率高;线程安全;反射无法破坏;代码简洁

总结

​ 一般情况下,我们只推荐后三种写法,都能做到在保证线程安全的同时,效率高,其中尤其推荐枚举写法,代码优雅,写法简单,还可以防止反射。

参考链接

posted @ 2022-02-02 21:41  Faetbwac  阅读(58)  评论(0编辑  收藏  举报