1.单例模式的核心目标:确保一个类在程序的整个生命周期中,有且只有一个实例,并提供一个全局访问点来获取这个实例。单例模式的本质就是只能自己创建对象,且只能创建一个对象(确保全局唯一实例)。
2.单例模式的应用场景:当多个模块需要共享同一个资源,或者创建某个对象的成本很高的时候,适合使用单例模式。
(1)日志记录器:整个日志系统使用同一个日志对象来记录信息,避免文件被多个对象同时写入造成错乱。
(2)数据库连接池:全局共享一个连接池对象,复用数据库连接,节省资源。
(3)配置信息管理:在程序启动时读取配置文件,之后所有模块都通过一个对象访问配置。
(4)硬件接口:比如打印机的后台处理程序,防止多个任务同时操作打印机。
3.实现单例模式的关键:
(1)私有化构造方法:防止外部通过new关键字创建对象。
(2)单例模式中,构造器是私有的,不允许外部创建对象,所以必须提供一个不依赖对象的访问方式。静态成员(静态变量和静态方法)不属于任何一个对象实例,而是属于类本身,它们在内存中有一块独立的存储区域,不依附于任何对象而存在。只有静态变量+静态方法,才能共同实现"不创建对象就能访问对象"。
(a)提供一个静态变量:在类的内部持有自己唯一的实例。
(b)提供一个公开的静态方法:作为全局访问点,让外部获取这个内部持有的唯一实例。
4.常见的实现方式:
(1)懒汉式:延迟加载,按需创建。也叫双重检查锁模式。在第一次调用时才会创建实例,在多线程环境下需要加锁保证线程安全。
(2)饿汉式:最简单、最常用。在类加载时就完成实例的创建,是线程安全的。缺点是如果这个类一直没被使用,会提取占用内存。
附代码:
一、懒汉式:双重检查锁模式。
java
// 懒汉式单例(双重检查锁定)
class SingletonLazy {
// 懒汉式的双重检查锁模式将对象的创建延迟到了运行时的首次调用getInstance()时,破坏了JVM的天然保障
// 因此必须手动使用volatile来禁止指令的重排,防止其他线程看到未初始化的对象
// 也就是说懒汉式的线程安全保障需要依赖于外界的volatile + synchronized双重机制
private static volatile SingletonLazy instance = null;
// 私有构造方法(函数)
private SingletonLazy() {}
// 双重检查锁定
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
public void showMessage() {
System.out.println("Lazy Singleton: Hello World!");
}
}
二、饿汉式:在类加载时就完成实例的创建,是线程安全的。
java
// 饿汉式单例
class SingletonHungry {
// 饿汉式是在类加载阶段创建对象实例,本身就是线程安全的,不需要使用volatile
private static final SingletonHungry instance = new SingletonHungry();
// 私有构造方法,防止外部实例化
private SingletonHungry() {}
// 提供全局访问点
public static SingletonHungry getInstance() {
return instance;
}
public void showMessage() {
System.out.println("Hungry Singleton: Hello World!");
}
}
ACM模式:
一、懒汉式:双重检查锁模式。
java
import java.util.Scanner;
// 懒汉式单例(双重检查锁定)
class SingletonLazy {
// 懒汉式的双重检查锁模式将对象的创建延迟到了运行时的首次调用getInstance()时,破坏了JVM的天然保障
// 因此必须手动使用volatile来禁止指令的重排,防止其他线程看到未初始化的对象
// 也就是说懒汉式的线程安全保障需要依赖于外界的volatile + synchronized双重机制
private static volatile SingletonLazy instance = null;
// 私有构造方法
private SingletonLazy() {}
// 双重检查锁定
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
public void showMessage() {
System.out.println("Lazy Singleton: Hello World!");
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
// 创建了两个对象引用(变量),在单例模式中,这两个引用指向的是同一个对象实例
// 如果不是单例模式且每次getInstance()都new对象,那么在绝大多数情况下每次都new的是新对象
// getInstance是静态方法,所以要用类名调用
SingletonLazy singleton1 = SingletonLazy.getInstance();
SingletonLazy singleton2 = SingletonLazy.getInstance();
// 由于这里的SingletonLazy是一个类,因此singleton1和singleton2都是对象引用
// 因此 == 比较的是两个引用指向的内存地址是否相等(是否指向了堆内存中的同一个对象实例)
System.out.println(singleton1 == singleton2);
// showMessage是实例方法,所以要用对象调用
singleton1.showMessage();
}
scanner.close();
}
}
二、饿汉式:在类加载时就完成实例的创建,是线程安全的。
java
import java.util.Scanner;
// 饿汉式单例
class SingletonHungry {
// 饿汉式是在类加载阶段创建对象实例,本身就是线程安全的,不需要使用volatile
private static final SingletonHungry instance = new SingletonHungry();
// 私有构造方法,防止外部实例化
private SingletonHungry() {}
// 提供全局访问点
public static SingletonHungry getInstance() {
return instance;
}
public void showMessage() {
System.out.println("Hungry Singleton: Hello World!");
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取操作次数
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
// 创建了两个对象引用(变量),在单例模式中,这两个引用指向的是同一个对象实例
// 如果不是单例模式且每次getInstance()都new对象,那么在绝大多数情况下每次都new的是新对象
// getInstance是静态方法,所以要用类名调用
SingletonHungry singleton1 = SingletonHungry.getInstance();
SingletonHungry singleton2 = SingletonHungry.getInstance();
// 由于这里的SingletonHungry是一个类,因此singleton1和singleton2都是对象引用
// 因此 == 比较的是两个引用指向的内存地址是否相等(是否指向了堆内存中的同一个对象实例)
System.out.println(singleton1 == singleton2);
// showMessage是实例方法,所以要用对象调用
singleton1.showMessage();
}
scanner.close();
}
}