单例模式的应用场景
以下是单例模式的一些应用场景:
在单例设计模式中,主要有以下几种常见的写法:饿汉式、懒汉式、静态内部类、枚举。每种写法都有其优缺点,具体应该根据实际需求和场景进行选择。
示例代码
方式一:饿汉式(使用静态变量)
package cn.leetcode.singleton;
public class Singleton {
// 私有构造方法,防止外部创建实例
private Singleton() {
}
// 静态实例,在类加载时初始化
private final static Singleton INSTANCE = new Singleton();
// 公有静态方法,返回唯一实例
public static Singleton getInstance(){
return INSTANCE;
}
}
还可以在静态代码块中创建单例:
class Singleton {
private Singleton() {
}
private static Singleton INSTANCE;
static {
// 在静态代码块中,创建单例对象
INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return INSTANCE;
}
}
测试方法:
package cn.leetcode.singleton;
public class SingletonTest {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance.equals(instance2));
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance.hashCode());
}
}
饿汉式是线程安全的,写法也相对简单,不容易出错。
方式二:懒汉式
提供一个静态的公有方法,当使用到该方法时,才去创建实例,再加入同步处理的代码,解决线程安全问题,即懒汉式。
package cn.leetcode.singleton2;
public class Singleton {
// 延迟初始化
private static Singleton INSTANCE = null;
// 私有构造方法,防止外部创建实例
private Singleton() {}
// 公有静态方法,返回唯一实例
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
上面的代码看起来没什么问题,但是当多个线程同时调用 getInstance() 方法时,可能会导致多个实例被创建。例如,线程 A 进入 if 条件判断语句块前,线程 B 也进入了该语句块,并创建了一个新的实例。当线程 A 继续执行时,它也会再次创建一个新的实例,这样就会导致多个实例存在。
为了解决这个问题,我们需要在方法内加上 synchronized 关键字,保证同一时刻只有一个线程能够进入方法并创建实例。但是这样会带来性能上的问题,因为每次获取实例都需要获得一个锁,并发性能会受到影响。
为了避免这个问题,我们可以使用双重检查(Double-Checked Locking)的方式,在加锁的前提下再次检查实例是否已经被创建。代码如下:
方式三:双重检查
提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题,同时保证了效率,推荐使用。
package cn.leetcode.singleton3;
public class Singleton {
// 使用 volatile 关键字确保 instance 变量的可见性、禁止指令重排序
private static volatile Singleton INSTANCE = null;
// 私有构造方法,防止外部创建实例
private Singleton() {
}
// 公有静态方法,返回唯一实例
public static Singleton getInstance() {
// 第一次检查,不需要加锁
if (INSTANCE == null) {
// 加锁
synchronized (Singleton.class) {
// 第二次检查,必须加锁
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
在这个实现中,我们使用了 volatile 关键字来保证 instance 变量的可见性,在多线程环境下可以正确地处理指令重排序等问题。同时,我们在第一次检查实例是否已经创建时不需要加锁,只有在需要创建实例时才进行加锁操作,避免了每次获取实例都需要获得锁的问题,提高了并发性能。
方式四:静态内部类
静态内部类方式是一种比较推荐的单例实现方式,利用 Java 类加载机制保证唯一实例,可以避免线程安全问题,同时也不会出现饿汉式的资源浪费问题。这种方式具体实现起来也比较简单。
package cn.leetcode.singleton4;
public class Singleton {
// 静态内部类,利用 Java 类加载机制保证唯一实例
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 私有构造方法,防止外部创建实例
private Singleton() {
}
// 公有静态方法,返回唯一实例
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
在上面的代码中,Singleton 类的构造方法是私有的,保证外部无法通过 new 关键字创建对象。静态内部类 SingletonHolder 是一个私有的静态内部类,它包含了一个静态常量 INSTANCE,该常量是 Singleton 的唯一实例。当我们调用 getInstance() 方法时,直接返回 SingletonHolder.INSTANCE 即可。
由于静态内部类的特性,它只有在第一次被使用的时候才会被加载和初始化,因此能够保证懒加载和线程安全。另外,由于 SingletonHolder是一个私有的内部类,所以它不会被外部访问到。
总之,相比于双重检查锁定等方式,使用静态内部类实现单例模式更加简洁、安全、并且具有良好的延迟加载能力。
方式五:使用枚举
当使用枚举类型来实现单例模式时,Java 会保证每个枚举常量在 JVM 中只有一个实例。因此,无论在多少次调用中都是同一个对象。这种方式不仅可以避免线程安全问题,也可以防止反射攻击和序列化问题。
enum Singleton {
// 属性
INSTANCE;
public void sayOK() {
System.out.println("ok");
}
}
在上面的代码中,Singleton 是一个枚举类型,定义了一个名为 INSTANCE 的枚举常量,它是 Singleton 类的唯一实例。由于枚举类型本身就是单例的,并且在加载枚举类型时就已经创建了 INSTANCE 实例,因此该方法返回的永远都是同一个对象。
我们可以通过 Singleton.INSTANCE 来获取该单例对象,并且可以调用其中的方法。例如:
Singleton singleton = Singleton.INSTANCE;
singleton.doSomething();
总结
单例模式是一种创建型设计模式,它可以确保一个类只有一个实例,并提供全局访问点。单例模式通常被用于管理全局状态以及控制资源的访问。在 Java 中,单例模式有多种实现方式,主要包括:
在实现单例时,需要考虑线程安全、延迟加载、序列化等问题,选择合适的实现方式能够提高程序的可读性、可维护性和性能表现。
常见的面试、笔试问题
单例模式在多线程环境中为什么需要特殊注意?请给出一种线程不安全的单例实现,并说明它的问题所在。
如何通过枚举类型来实现单例模式?与其他实现方式相比,它有哪些优势?
在 Java 中,我们可以使用枚举类型来实现单例模式。这种方式的实现非常简单,在加载枚举类型时就已经创建了唯一的实例,并且在程序运行期间保证只有一个实例存在。以下是一个使用枚举类型实现单例模式的示例:
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Do something...");
}
}
相比于其他实现方式,使用枚举类型实现单例模式有以下优势:
阅读量:2042
点赞量:0
收藏量:0