Java设计模式之单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。以下是单例模式的几种常见实现方式:

一、饿汉式(线程安全)

public class Singleton {
    // 1.静态 final 修饰,类加载时初始化
    private static final Singleton instance = new Singleton();

    // 2.私有构造方法,防止外部实例化
    private Singleton() {}

    // 3.提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}
// 在其他类中使用
public class OtherClass {
    public void someMethod() {
        // 获取单例实例
        Singleton singleton = Singleton.getInstance();
        // 调用单例实例的方法
        singleton.doSomething();
    }
}

为什么叫“饿汉式”?

“饿汉式”这个名字来源于它的行为特点。在饿汉式单例模式中,实例在类加载时就被创建,就好像一个饥饿的人(饿汉)迫不及待地吃掉食物一样。它在类加载时就“吃掉”了资源,确保了实例的唯一性和线程安全性。

为什么线程安全?

饿汉式单例模式的线程安全性主要依赖于Java的类加载机制。在Java中,类的静态变量在类加载时被初始化,并且类加载过程是由JVM保证线程安全的。因此,当多个线程同时调用getInstance()方法时,由于实例已经在类加载时被创建,所以不会出现多次创建实例的问题。

核心解析

static 和 final 的作用

static

static用于声明一个静态变量instance,它属于类本身而不是类的某个实例。静态变量在类加载时被初始化,并且在内存中只有一份副本,所有类的实例共享这个变量。

在单例模式中,static确保instance是类级别的变量,而不是实例级别的变量,这样可以保证只有一个实例存在。

final

final用于声明一个最终变量instance,它的值一旦被初始化后不能被修改。

在单例模式中,final确保instance只能被赋值一次,从而保证单例的唯一性。

结合使用的效果

确保只有一个 new Singleton()。通过 static 和 final 的结合,instance 在类加载时被初始化,并且只能被赋值一次。这样就确保了在整个应用程序中,只有一个 Singleton 实例被创建。

private Singleton() {} 的作用

将构造方法声明为私有,防止外部通过new Singleton()的方式创建类的实例。

在单例模式中,我们希望类的实例只能通过getInstance()方法获取,而不是通过直接调用构造方法创建。

如果没有私有构造方法,外部代码可以通过new Singleton()创建多个实例,破坏单例模式的唯一性。

public static Singleton getInstance() 的使用

提供一个公共的静态方法,用于获取单例实例。

通过这个方法,外部代码可以访问唯一的单例实例,而不需要知道实例的创建细节。

二、懒汉式(线程不安全)

为什么需要懒汉式单例模式?

1.资源优化

如果实例创建成本较高(如数据库连接池、大型对象等),而实例可能在整个应用程序运行过程中都不被使用,那么饿汉式会在类加载时就创建实例,造成资源浪费。懒汉式则在第一次使用时才创建实例,避免了这种浪费。

2.延迟初始化

在某些情况下,实例的初始化可能依赖于某些运行时条件或配置。懒汉式可以在满足这些条件时才初始化实例,从而提高系统的灵活性。

3.提高启动速度

如果实例的初始化过程较复杂且耗时,而应用程序在启动时并不立即需要该实例,那么懒汉式可以延迟初始化,从而提高应用程序的启动速度。

代码示例

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

为什么第一个方法是线程不安全的

第一个方法是线程不安全的,因为在多线程环境下,可能会有多个线程同时通过 if (instance == null) 检查,从而导致多次创建 Singleton 实例。

示例说明

假设两个线程同时调用 getInstance() 方法:

1.线程 A 和 线程 B 同时调用 getInstance()

2.线程 A 和 线程 B 同时检查 if (instance == null),发现 instancenull

3.线程 A 和 线程 B 都进入 if 块,分别创建 Singleton 实例。

4.最终,Singleton 类被创建了两次,破坏了单例模式的唯一性。

如何处理线程安全问题

为了确保线程安全,可以使用 synchronized 关键字来同步 getInstance() 方法。这样可以确保在同一时间只有一个线程可以进入 getInstance() 方法,从而避免多个线程同时创建实例。

详细解释

1.synchronized 关键字:

  • synchronized 用于确保同一时间只有一个线程可以进入被修饰的方法或代码块。
  • getInstance() 方法上使用 synchronized,可以确保在多线程环境下,只有一个线程可以执行 getInstance() 方法的代码块,从而避免多个线程同时创建实例。

2.线程安全的实现

当 线程 A 进入 getInstance() 方法时,其他线程(如 线程 B)会被阻塞,等待 线程 A 完成实例化。线程 B 在进入 getInstance() 方法时,instance 已经被 线程 A 创建,因此不会再次创建实例。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

三、静态内部类实现

1.线程安全

静态内部类的加载是由JVM控制的,JVM会确保在类初始化时只有一个线程能够执行初始化操作,因此这种实现方式是线程安全的。

2.懒加载

静态内部类 SingletonHolder 只有在第一次调用 getInstance() 方法时才会被加载,从而初始化 Singleton 实例。这实现了懒加载,避免了在类加载时就初始化实例。

3.性能优化

不需要使用 synchronized 关键字来保证线程安全,因此在实例创建后,多次调用 getInstance() 方法时不会有同步开销。

4.简洁性

实现相对简洁,避免了复杂的同步逻辑。

总结:静态内部类实现单例模式是一种优雅的实现方式,它结合了饿汉式和懒汉式的优势,既保证了线程安全,又实现了懒加载,同时避免了同步带来的性能开销。推荐在需要线程安全且希望懒加载的场景中使用这种实现方式。

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

四、枚举实现

枚举实现单例模式是一种简洁且线程安全的方式,它利用了 Java 枚举类型的特性来确保单例的唯一性和线程安全性。以下是枚举实现单例模式的详细解析:

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 方法实现
        System.out.println("Doing something...");
    }
}

1.线程安全

Java 枚举类型的实例创建是线程安全的,JVM 会确保在类加载时只有一个实例被创建。

枚举的实例化过程是由 JVM 控制的,因此不需要额外的同步措施。

2.防止反序列化

枚举类型在反序列化时不会创建新的实例,因此可以防止通过反序列化破坏单例模式。

3.简洁性

枚举实现单例模式的代码非常简洁,不需要额外的 getInstance() 方法或其他复杂的逻辑。

4.唯一性

枚举类型在 Java 中是 final 的,不能被继承,因此可以确保只有一个实例存在。

public class OtherClass {
    public void someMethod() {
        // 获取单例实例
        Singleton singleton = Singleton.INSTANCE;
        // 调用单例实例的方法
        singleton.doSomething();
    }
}

总结:枚举实现单例模式是一种简洁、线程安全且防止反序列化的单例实现方式。它利用了 Java 枚举类型的特性,确保了单例的唯一性和线程安全性。推荐在需要线程安全且防止反序列化的场景中使用这种实现方式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值