【Java】单例模式的五种实现方式以及为什么推荐枚举类

举报
秋日的晚霞 发表于 2022/05/14 22:33:53 2022/05/14
【摘要】 【Java】单例模式的五种实现方式以及为什么推荐枚举类 1. 饿汉式构造私有 静态成员 提供方法获取public class SingleTarget { private static SingleTarget instance = new SingleTarget(); private SingleTarget(){}; public static SingleTa...

【Java】单例模式的五种实现方式以及为什么推荐枚举类

image-20220514205504118

1. 饿汉式

构造私有 静态成员 提供方法获取

public class SingleTarget {

    private static SingleTarget instance = new SingleTarget();

    private SingleTarget(){};

    public static SingleTarget getInstance()
    {
        return instance;
    }

}


测试一下

    public static void main(String[] args) {

        SingleTarget instance1 = SingleTarget.getInstance();
        SingleTarget instance2 = SingleTarget.getInstance();

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

    }

由于 是静态的 不管你有没有调用方法去拿这个实例,他也会先加载到内存中

同时,还能通过反射创建出多个对象

//反射创建单例对象
        Class<SingleTarget> aClass = (Class<SingleTarget>) Class.forName("单例模式.饿汉式.SingleTarget");
        //获取构造方法对象
        Constructor<SingleTarget> constructor =  aClass.getDeclaredConstructor();
        //开启暴力反射
        constructor.setAccessible(true);
        //调用构造方法创建对象
        SingleTarget instance2 = constructor.newInstance();

        SingleTarget instance1 = SingleTarget.getInstance();

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

image-20220514210438086

2. 懒汉式

懒汉式解决了饿汉式不能懒加载这么一个问题, 但也存在反射创建多个对象这么一个问题 且 是线程不安全的

public class SingleTarget {

    private static SingleTarget instance;

    private SingleTarget() {
    }

    ;

    public static SingleTarget getInstance() {
        if (null == instance) {
            instance = new SingleTarget();
        }
        return instance;
    }

}

反射在饿汉式中已经演示过了 这里就不在演示了 演示下线程安全问题

public class Test {
    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(16);

        IntStream.rangeClosed(0,100).parallel().forEach(i->{
            executorService.submit(()->{
                System.out.println("i = " + i);
                SingleTarget instance1 = SingleTarget.getInstance();
                SingleTarget instance2 = SingleTarget.getInstance();
                if (instance1 != instance2){

                    System.out.println("出现线程安全问题");
                    System.out.println("instance1 = " + instance1);
                    System.out.println("instance2 = " + instance2);
                }

            });
        });


        //SingleTarget instance1 = SingleTarget.getInstance();
        //SingleTarget instance2 = SingleTarget.getInstance();
        //
        //System.out.println( instance1 == instance2);
    }

}

idea64_GFDh3MeIxs

为什么会有线程安全问题呢 ? 看我们的代码 当两个线程同时走到这一步的时候,都会进if 结果 创建了两个对象 你可能会说,那加锁啊

是的 加锁 因此有了双检锁模式

image-20220514212019291

3. 双检锁模式

懒汉式存在线程安全问题, 是的 加锁能解决一切线程安全问题 但是要考虑效率问题 锁加在什么地方合适呢?

image-20220514212425549

不管是不是第一次获取实例,都得等一下 效率非常低 怎么样分流呢 ?

image-20220514212628981

改成这样, 咋一看好像没什么问题 但是同样的这还是存在线程安全问题

image-20220514212809583

如果首次调用的时候多个线程同时走到 if 判断 而此时 instance 确实是 null, 那么都会进入 if 虽然同时只能一个线程去new 这个对象,但是后面的线程也能执行 所以这样写时有问题的 应该要改成这样

public class SingleTarget {

    private static SingleTarget instance;

    private SingleTarget() {};

    public static  SingleTarget getInstance() {
        if (null == instance) {
            synchronized (SingleTarget.class){
                if (null == instance) {
                    instance = new SingleTarget();
                }
            }
        }
        return instance;
    }

}

那改成这样是不是也没问题了 ? 其实这里还存在一个小问题 由于

instance = new SingleTarget();

并不是一个原子操作 可能会存在指令重排 所以最好是加上 volatile 关键字

public class SingleTarget {

    private static volatile SingleTarget instance;

    private SingleTarget() {};

    public static  SingleTarget getInstance() {
        if (null == instance) {
            synchronized (SingleTarget.class){
                if (null == instance) {
                    instance = new SingleTarget();
                }
            }
        }
        return instance;
    }

}

双检锁模式下 又是加锁又是双重检查 特别麻烦 并且也能通过反射创建多个对象

所以我们看下一种实现方法

4. 静态内部类

public class SingleTarget {
    
    private SingleTarget() {};

    public static class SingleTargetUtilClass{
        private static final SingleTarget instance = new SingleTarget();
    }

    public static SingleTarget getInstance()
    {
        return SingleTargetUtilClass.instance;
    }
}

与双检锁模式相比 更加简单,同时也不存在线程安全问题也能做到懒加载

但是也存在线程安全问题

image-20220514214823099

有没有十全十美的方式呢 有 枚举

5. 枚举类

public enum SingleTarget {

    INSTANCE;

    public void doAny()
    {
        System.out.println("做你想做");
    }
}

如果你想通过反射创建枚举类的实例

    Constructor<SingleTarget> declaredConstructor = SingleTarget.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        SingleTarget singleTarget = declaredConstructor.newInstance();

会给你抛出异常

Cannot reflectively create enum objects

image-20220514222411524

`newInstance 方法 会进行判断 如果是枚举类 直接抛出异常

image-20220514222519890

或许你用的最多的是饿汉式去实现单例模式 但其实你可以尝试着用枚举类去实现一下 推荐 静态内部类和枚举类

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。