zoukankan      html  css  js  c++  java
  • Java单例模式:为什么我强烈推荐你用枚举来实现单例模式

    单例模式简介

    单例模式是 Java 中最简单,也是最基础,最常用的设计模式之一。在运行期间,保证某个类只创建一个实例,保证一个类仅有一个实例,并提供一个访问它的全局访问点。下面就来讲讲Java中的N种实现单例模式的写法。

    饿汉式

    public class Singleton {
    
        private static Singleton instance = new Singleton();
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            return instance;
        }
    
    }
    
    

    这是实现一个安全的单例模式的最简单粗暴的写法,这种实现方式我们称之为饿汉式。之所以称之为饿汉式,是因为肚子很饿了,想马上吃到东西,不想等待生产时间。这种写法,在类被加载的时候就把Singleton实例给创建出来了。

    饿汉式的缺点就是,可能在还不需要此实例的时候就已经把实例创建出来了,没起到lazy loading的效果。优点就是实现简单,而且安全可靠。

    懒汉式

    public class Singleton {
        
        private static Singleton instance;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
        
    }
    

    相比饿汉式,懒汉式显得没那么“饿”,在真正需要的时候再去创建实例。在getInstance方法中,先判断实例是否为空再决定是否去创建实例,看起来似乎很完美,但是存在线程安全问题。在并发获取实例的时候,可能会存在构建了多个实例的情况。所以,需要对此代码进行下改进。

    public class SingletonSafe {
    
        private static volatile SingletonSafe singleton;
    
        private SingletonSafe() {
        }
    
        public static SingletonSafe getSingleton() {
            if (singleton == null) {
                synchronized (SingletonSafe.class) {
                    if (singleton == null) {
                        singleton = new SingletonSafe();
                    }
                }
            }
            return singleton;
        }
    }
    

    这里采用了双重校验的方式,对懒汉式单例模式做了线程安全处理。通过加锁,可以保证同时只有一个线程走到第二个判空代码中去,这样保证了只创建 一个实例。这里还用到了volatile关键字来修饰singleton,其最关键的作用是防止指令重排。

    静态内部类

    public class Singleton {
    
        private static class SingletonHolder {
            private static Singleton instance = new Singleton();
        }
    
        private Singleton() {
            
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    }
    

    通过静态内部类的方式实现单例模式是线程安全的,同时静态内部类不会在Singleton类加载时就加载,而是在调用getInstance()方法时才进行加载,达到了懒加载的效果。

    似乎静态内部类看起来已经是最完美的方法了,其实不是,可能还存在反射攻击或者反序列化攻击。且看如下代码:

    public static void main(String[] args) throws Exception {
        Singleton singleton = Singleton.getInstance();
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton newSingleton = constructor.newInstance();
        System.out.println(singleton == newSingleton);
    }
    

    运行结果:

    通过结果看,这两个实例不是同一个,这就违背了单例模式的原则了。

    除了反射攻击之外,还可能存在反序列化攻击的情况。如下:

    引入依赖:

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
    

    这个依赖提供了序列化和反序列化工具类。

    Singleton类实现java.io.Serializable接口。

    如下:

    public class Singleton implements Serializable {
    
        private static class SingletonHolder {
            private static Singleton instance = new Singleton();
        }
    
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            return SingletonHolder.instance;
        }
    
        public static void main(String[] args) {
            Singleton instance = Singleton.getInstance();
            byte[] serialize = SerializationUtils.serialize(instance);
            Singleton newInstance = SerializationUtils.deserialize(serialize);
            System.out.println(instance == newInstance);
        }
    
    }
    

    运行结果:

    通过枚举实现单例模式

    在effective java(这本书真的很棒)中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。

    public enum Singleton {
    
        INSTANCE;
    
        public void doSomething() {
            System.out.println("doSomething");
        }
    
    }
    

    调用方法:

    public class Main {
    
        public static void main(String[] args) {
            Singleton.INSTANCE.doSomething();
        }
    
    }
    
    

    直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。

    总结

    以上列举了多种单例模式的写法,分析了其利弊之处。同时还介绍了目前最佳的单例写法——枚举模式,相信在未来,枚举模式的单例写法也会越来越流行。

  • 相关阅读:
    每日日报2020.12.1
    每日日报2020.11.30
    981. Time Based Key-Value Store
    1146. Snapshot Array
    565. Array Nesting
    79. Word Search
    43. Multiply Strings
    Largest value of the expression
    1014. Best Sightseeing Pair
    562. Longest Line of Consecutive One in Matrix
  • 原文地址:https://www.cnblogs.com/happy4java/p/11206105.html
Copyright © 2011-2022 走看看