美文网首页
项目实战—那些年常用的单例模式

项目实战—那些年常用的单例模式

作者: 小胖学编程 | 来源:发表于2021-06-12 06:26 被阅读0次

    常见的单例模式:饿汉式、懒汉式、双重检查锁模式、静态内部类实现单例模式、枚举单例模式,本文重点是在项目中如何实现上述的单例模式。

    1. 饿汉式单例模式

    饿汉式单例:类初始化时将单例对象加载到JVM中。

    /**
     * 饿汉单例模式。
     * 类初始化时将单例对象加载到JVM中。
     */
    @Slf4j
    public class Singletoneh {
        private final static Singletoneh instance = new Singletoneh();
    
        private Singletoneh() {
        }
    
        public static Singletoneh getInstance() {
            return instance;
        }
    
        public void say() {
            System.out.println("【饿汉模式】—实现单例!");
        }
    
    }
    

    2. 懒汉式单例模式

    懒汉式单例:并发写时,存在线程安全问题。进化版:双重检查锁模式

    /**
     * 懒汉式单例
     */
    public class Singletonlh {
        private static Singletonlh instance;
        private Singletonlh() {
    
        }
        public static Singletonlh getInstance() {
            if (instance == null) {
                instance = new Singletonlh();
            }
            return instance;
        }
        public void say() {
            System.out.println("【懒汉模式】—实现单例!");
        }
    }
    

    3. 双重检查锁模式

    volatile关键字详见—Volatile可见性原理

    /**
     * 双重检查锁模式
     * volatile 关键字:防止指令重排
     * <p>
     * 被volatile修饰的变量,会加一个lock前缀的汇编指令。
     * 若变量被修改后,会立刻将变量由工作内存回写到主存中。那么意味了之前的操作已经执行完毕。这就是内存屏障。
     */
    public class SingletonOfSync2 {
        private static volatile SingletonOfSync2 instance;
        private SingletonOfSync2() {
    
        }
        /**
         * 双重检查模式,防止并发写时创建多个实例对象。
         * 使用volatile关键字防止指令重排;
         *
         * @return
         */
        public static SingletonOfSync2 getInstance() {
            if (instance == null) {
                synchronized (SingletonOfSync2.class) {
                    if (instance == null) {
                        instance = new SingletonOfSync2();
                    }
                }
            }
            return instance;
        }
        public void say() {
            System.out.println("【双重检查锁模式】—实现单例!");
        }
    }
    

    4. 静态内部类单例模式

    由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序。

    /**
     * 静态内部类实现单例模式
     * <p>
     * 由于 JVM 在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类的属性/方法被调用时才会被加载, 并初始化其静态属性。
     * 静态属性由static修饰,保证只被实例化一次,并且严格保证实例化顺序。
     */
    public class SingletonOfInner {
        private SingletonOfInner() {
    
        }
        private static class InstanceHolder {
            private final static SingletonOfInner instance = new SingletonOfInner();
        }
        public static SingletonOfInner getInstance() {
            return InstanceHolder.instance;
        }
        public void say() {
            System.out.println("【静态内部类模式】—实现单例!");
        }
    }
    

    5. 枚举类单例模式

    因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。

    public class SingletonOfEnum {
    
        //私有构造方法
        private SingletonOfEnum() {
    
        }
        /**
         * 枚举类返回单例对象
         */
        public static SingletonOfEnum getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    
        private enum Singleton {
    
            INSTANCE;
    
            private final SingletonOfEnum instance;
    
            Singleton() {
                instance = new SingletonOfEnum();
            }
    
            private SingletonOfEnum getInstance() {
                return instance;
            }
    
        }
    
        public void say() {
            System.out.println("【枚举模式】—实现单例!");
        }
    
    }
    

    6. 破坏单例模式以及解决方案

    6.1 反射破坏

    public class testSingleton {
        public static void main(String[] args) throws Exception {
    
            Constructor<Singletoneh> co1 = Singletoneh.class.getDeclaredConstructor();
            co1.setAccessible(true);
            Singletoneh s1 = co1.newInstance();
            s1.say();
    
            Constructor<Singletoneh> co2 = Singletoneh.class.getDeclaredConstructor();
            co2.setAccessible(true);
            Singletoneh s2 = co2.newInstance();
            s2.say();
    
            System.out.println("单例对象是否相等:" + (s1 == s2));
            
        }
    }
    
    image.png

    除枚举方式外, 其他方法都会通过反射的方式破坏单例,反射是通过调用构造方法生成新的对象,所以如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例, 则阻止生成新的实例,解决办法如下:

        private Singletoneh() {
            if(instance!=null){
                throw new RuntimeException("单例对象已经存在");
            }
        }
    

    6.2 序列化接口Serializable

    如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。

    public Object readResolve() throws ObjectStreamException {
        return instance;
    }
    

    推荐阅读

    枚举类来实现单例模式

    相关文章

      网友评论

          本文标题:项目实战—那些年常用的单例模式

          本文链接:https://www.haomeiwen.com/subject/nxkpeltx.html