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

简介: # 【Java】单例模式的五种实现方式以及为什么推荐枚举类![image-20220514205504118](https://img-blog.csdnimg.cn/img_convert/2ca71ab97f5aeba284d8155b366615d4.png)## 1. 饿汉式构造私有 静态成员 提供方法获取```public class SingleTarget { private static SingleTarget instance = new SingleTarget(); private SingleTarget(){};

【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

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

相关文章
|
2月前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
160 57
|
13天前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
|
2月前
|
Java
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
java小工具util系列4:基础工具代码(Msg、PageResult、Response、常量、枚举)
57 24
|
2月前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
66 8
|
2月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
42 4
|
2月前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
77 17
|
2月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
2月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
138 4
|
2月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
86 2
|
2月前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
61 2