设计模式(三) 单例模式

简介: 单例模式也是一种创建型模式,而且也非常容易理解:在一个系统中可能需要多个配置文件,我们希望这些配置文件的实例只存在一个,而不是存在多个重复的实例。这时候就需要使用单例模式。

单例模式也是一种创建型模式,而且也非常容易理解:在一个系统中可能需要多个配置文件,我们希望这些配置文件的实例只存在一个,而不是存在多个重复的实例。这时候就需要使用单例模式。

单例模式有几个要点:

  • 一是必须确保只存在一个类的实例。
  • 二是类必须自己创建自己,不允许其他类来创建自己。
  • 三是必须提供一个方法允许其他类访问单例成员。

根据这些特点,我们可以很容易猜出单例类在Java的样子:首先他的构造方法必须是私有的,然后往往需要一个公有的静态方法获取单例实例。

单例的实现

单例模式的实现有很多种,按照单例的实例化的时机可以分为饿汉式和懒汉式两种,下面来逐一说明。

懒汉式(非线程安全)

这种方式非常简单,也很容易理解。单例实例在第一次调用的时候才创建,符合懒加载的要求。唯一缺点是这种方式不支持多线程,在多线程环境下可能会创建多个对象。

public class UnThreadSafeSingleton {
    private UnThreadSafeSingleton() {
    }

    private static UnThreadSafeSingleton singleton;

    public static UnThreadSafeSingleton getSingleton() {
        if (singleton == null) {
            singleton = new UnThreadSafeSingleton();
        }
        return singleton;
    }
}

懒汉式(同步的)

我们可以对上面的实现方式进行改进,以便在多线程环境下也可以正常工作。实现方式很简单,直接在方法上添加synchronized关键字即可。

这种实现方式虽然也很简单,但是性能不咋地。由于直接在方法上加了锁,所以如果同时有两个地方获取单例对象,其中一个就会阻塞。在获取单例的次数获取比较多的时候性能很差。

public class SynchronizedThreadSafeSingleton {
    private static SynchronizedThreadSafeSingleton singleton;

    private SynchronizedThreadSafeSingleton() {

    }

    public synchronized static SynchronizedThreadSafeSingleton getSingleton() {
        if (singleton == null) {
            singleton = new SynchronizedThreadSafeSingleton();
        }
        return singleton;
    }
}

饿汉式(静态初始化)

如果不要求必须懒加载,那么我们可以使用JVM的类加载工作机制,方便的实现单例模式。

JVM在第一次加载类的时候,会被初始化累的静态域,并确保静态域只初始化一次。所以我们可以将创建单例的代码放到静态初始化块中,这样JVM会帮我们创建单例。这种方式的缺点就是加载类的时候就创建了单例对象,没有懒加载。

public class FirstLoadSingleton {
    private static FirstLoadSingleton singleton;

    private FirstLoadSingleton() {
    }

    static {
        singleton = new FirstLoadSingleton();
    }

    public static FirstLoadSingleton getSingleton() {
        return singleton;
    }
}

双检锁方式

这种方式比较复杂,但是其他方面都很好:既实现了懒加载,同时也是线程安全的,性能还不错。

双检锁模式的要点:一是单例必须使用volatile关键字标记;二是在创建单例的时候要进行两次检查(这就是双检锁的含义)。我们可以看到同步块在第一次判断之后,也就是说只有在第一次调用时才可能发生竞争和阻塞。单例创建之后,在获取单例的时候不会调用同步块,因此速度会非常快。和前面的直接在方法上添加同步的例子相比,真是不知道高到哪里去了。

public class DoubleCheckLockSingleton {
    private volatile static DoubleCheckLockSingleton singleton;

    private DoubleCheckLockSingleton() {
    }

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

静态内部类方式

这种方式和双检锁方式的效果类似,既可以保证懒加载又具有多线程下的性能优势。而且实现起来更加简单。唯一缺点就是单例对象必须是静态的,而双检锁方式的单例对象可以是实例的。

道理也很简单,如果我们把单例放到类的静态字段上,不能保证延迟加载的话,那么再用一层内部类包住不就行了。这样,当外层类第一次加载的时候,不会触发单例的初始化。而在第一次获取单例的时候,才会调用内部类,从而让JVM加载单例。

public class InnerClassSingleton {
    private static class Inner {
        private static InnerClassSingleton singleton = new InnerClassSingleton();
    }

    private InnerClassSingleton() {
    }

    public static InnerClassSingleton getSingleton() {
        return Inner.singleton;
    }
}

枚举方式

这种方式是Java实现单例最好的方式,连《Effective Java》都推荐我们使用这种方式。不过现在貌似使用的还是比较少。一来,枚举是Java 1.5才加入的东西;二来,Java的枚举使用起来确实很捉急。甚至有些开发实践都要求不使用枚举,而是使用共有静态字段来代替。所以枚举单例这种方式就比较稀少了。

不过确实,Java的枚举天生就是为实现单例而存在的。首先,枚举的实例是在使用时才被初始化的,这和单例模式延迟加载的要求相符。其次,枚举类型只允许存在私有的构造函数,从根本上杜绝了创建多个单例的可能性。而且当枚举序列化和反序列化的时候,同样会保证单例的唯一性。因此我们说,枚举方式是Java实现单例最好的方式。

可能还是不太好理解,所以还是直接看代码吧。假设我们需要一个单例的配置对象,我们可以创建枚举来解决。枚举的构造方法默认(且只能)是私有的,我们直接在构造方法中初始化数据(例如从文件读取等等),然后通过枚举类中定义的方法来读取数据。如果对Java的枚举还是感觉到比较陌生的话回去复习一下枚举类的用法。

public enum EnumSingleton {
    Instance;

    private String data;

    EnumSingleton() {
        //在构造方法中进行初始化
        data = "Some data";
    }

    public String getData() {
        return data;
    }
}

当然在现在的Java生态中单例模式一般不需要我们手动实现了。像Spring和Guice这样的依赖注入框架已经实现了单例模式,所以我们在使用这些框架的时候,创建和确保单例的工作有这些框架完成,我们只需要编写传统的非线程安全类即可。

相关文章
|
10月前
|
设计模式 缓存 安全
【设计模式】【创建型模式】单例模式(Singleton)
一、入门 什么是单例模式? 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。它常用于需要全局唯一对象的场景,如配置管理、连接池等。 为什么要单例模式? 节省资源 场景:某些对象创
376 15
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
142 2
|
设计模式 安全 Java
设计模式:单例模式
单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。它通过私有化构造函数、自行创建实例和静态方法(如`getInstance()`)实现。适用于数据库连接池、日志管理器等需要全局唯一对象的场景。常见的实现方式包括饿汉式、懒汉式、双重检查锁、静态内部类和枚举。线程安全问题可通过`synchronized`或双重检查锁解决,同时需防止反射和序列化破坏单例。优点是避免资源浪费,缺点是可能增加代码耦合度和测试难度。实际开发中应优先选择枚举或静态内部类,避免滥用单例,并结合依赖注入框架优化使用。
|
11月前
|
设计模式 存储 安全
设计模式-单例模式练习
单例模式是Java设计模式中的重要概念,确保一个类只有一个实例并提供全局访问点。本文详解单例模式的核心思想、实现方式及线程安全问题,包括基础实现(双重检查锁)、懒汉式与饿汉式对比,以及枚举实现的优势。通过代码示例和类图,深入探讨不同场景下的单例应用,如线程安全、防止反射攻击和序列化破坏等,展示枚举实现的简洁与可靠性。
192 0
|
设计模式 存储 安全
设计模式2:单例模式
单例模式是一种创建型模式,确保一个类只有一个实例,并提供全局访问点。分为懒汉式和饿汉式: - **懒汉式**:延迟加载,首次调用时创建实例,线程安全通过双重检查锁(double check locking)实现,使用`volatile`防止指令重排序。 - **饿汉式**:类加载时即创建实例,线程安全但可能浪费内存。 示例代码展示了如何使用Java实现这两种模式。
289 4
|
设计模式 存储 前端开发
前端必须掌握的设计模式——单例模式
单例模式是一种简单的创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。适用于窗口对象、登录弹窗等场景,优点包括易于维护、访问和低消耗,但也有安全隐患、可能形成巨石对象及扩展性差等缺点。文中展示了JavaScript和TypeScript的实现方法。
610 13
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
212 2
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
232 4
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
167 1

热门文章

最新文章