【Java设计模式 | 创建者模式】单例模式

前言:

创建者模式主要关注是"怎么创建对象?",它的主要特点是将对象的创建和使用分离。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建模式分为:

单例模式(Singleton):【Java设计模式 | 创建者模式】单例模式-CSDN博客

工厂方法模式(Factory Method)

抽象工厂模式(Abstract Factory)

建造者模式(Builder)

原型模式(Prototype)

单例设计模式:

单例模式(Singleton)是Java中最简单的设计模式之一。它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有一个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式结构:

单例模式主要有以下角色:

单例类:只能创建一个实例的类。

访问类:使用单例类。

单例模式的实现:

单例模式主要分为:

**饿汉式:**类加载就会导致该单实例对象被创建,避免了线程同步问题。

**懒汉式:**类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建。

一、饿汉式实现:

优点:实现简单,线程安全

缺点:可能造成资源浪费(如果实例未被使用)

1.静态变量方式:

私有构造函数确保外部无法通过new创建实例。

通过静态方法getInstance()提供全局唯一访问点。

java 复制代码
public class Singleton {
    // 在类加载时创建实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数防止外部实例化
    private Singleton() {}

    // 提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}
 

**2.静态代码块:**这种实现方式与直接静态变量方式的本质相同。静态代码块方式适合需要更复杂初始化逻辑的场景。

私有构造函数防止外部实例化

全局唯一访问点通过getInstance()方法提供

java 复制代码
public class Singleton {
    // 静态实例变量
    private static Singleton instance;
    
    // 静态代码块初始化实例
    static {
        instance = new Singleton();
    }
    
    // 私有构造函数防止外部实例化
    private Singleton() {}
    
    // 提供全局访问点
    public static Singleton getInstance() {
        return instance;
    }
}
 

二、懒汉式实现:

懒汉式单例模式的特点是延迟初始化,即在第一次调用时才创建实例。(需要判断instance是否为null)

**3.线程不安全:**如果有多个线程,可能存在创建多个实例的情况,导致线程不安全。

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

**4.线程安全:可以通过添加synchronized**关键字实现线程安全。

java 复制代码
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

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

**5.双重检查锁:**直接同步整个方法会影响性能,可以使用双重检查锁定来减少同步范围。

注意**volatile**关键字的使用,它防止指令重排序导致的初始化问题(例如多线程情况下,可能出现空指针问题)。设计到并发编程就不过多讲解。

java 复制代码
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

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

**6.静态内部类(推荐):**由于JVM在加载外部类的过程中,不会加载静态内部类,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证之后被实例化一次,并且严格保证实例化顺序。线程安全由JVM保证。

相对于多重检查锁实现单例模式,静态内部类的方式在保证了线程安全的前提下,实现更加的简单。

java 复制代码
public class Singleton {
    private Singleton() {
        // 私有构造函数,防止外部实例化
    }

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

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

**7.枚举方式:**枚举类是单例模式实现极力推荐的方式。属于饿汉式方式,在不考虑内存空间的情况下,首先考虑枚举方式。

优点:

  • 这是实现单例模式最简洁安全的方式
  • 天生支持序列化机制
  • 防止反射攻击
  • 线程安全由JVM保证
java 复制代码
public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 业务方法
    }
}
 

单例模式的破坏:

使上面定义的单例类(Singleton)可以创建多个对象(枚举方式除外)。有两种方式:序列化和反射。

**序列化和反序列化:**通过序列化和反序列化可以破坏单例模式,关键在于反序列化时会创建新的对象实例。

java 复制代码
import java.io.*;

// 可被序列化破坏的单例类
class Singleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static Singleton instance = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance;
    }
}

public class BreakSingleton {
    public static void main(String[] args) throws Exception {
        Singleton instance1 = Singleton.getInstance();
        
        // 序列化到文件
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
        out.writeObject(instance1);
        out.close();
        
        // 反序列化
        ObjectInput in = new ObjectInputStream(new FileInputStream("singleton.ser"));
        Singleton instance2 = (Singleton) in.readObject();
        in.close();
        
        System.out.println("instance1 hashCode: " + instance1.hashCode());
        System.out.println("instance2 hashCode: " + instance2.hashCode());
        System.out.println("Is same instance? " + (instance1 == instance2));
    }
}
 

防止序列化和反序列化破坏的方法:实现readResolve()方法 是防止序列化破坏单例的关键。当JVM反序列化对象时,会检查类是否定义了此方法。如果存在,JVM会自动调用该方法获取对象,而不是新建实例。

java 复制代码
import java.io.*;

// 防止序列化破坏的单例类
class SafeSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static SafeSingleton instance = new SafeSingleton();
    
    private SafeSingleton() {}
    
    public static SafeSingleton getInstance() {
        return instance;
    }
    
    // 关键方法:反序列化时返回已有实例
    protected Object readResolve() {
        return instance;
    }
}

public class SafeSingletonDemo {
    public static void main(String[] args) throws Exception {
        SafeSingleton instance1 = SafeSingleton.getInstance();
        
        // 序列化
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream("safe.ser"));
        out.writeObject(instance1);
        out.close();
        
        // 反序列化
        ObjectInput in = new ObjectInputStream(new FileInputStream("safe.ser"));
        SafeSingleton instance2 = (SafeSingleton) in.readObject();
        in.close();
        
        System.out.println("instance1 hashCode: " + instance1.hashCode());
        System.out.println("instance2 hashCode: " + instance2.hashCode());
        System.out.println("Is same instance? " + (instance1 == instance2));
    }
}
 

**反射破坏:**单例模式通常通过私有构造函数和静态方法确保只有一个实例。反射可以绕过这些限制,直接调用私有构造函数创建新实例。

java 复制代码
import java.lang.reflect.Constructor;

public class BreakSingleton {
    public static void main(String[] args) throws Exception {
        Singleton instance1 = Singleton.getInstance();

        // 获取私有构造函数
        Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true); // 突破私有访问限制

        // 创建第二个实例
        Singleton instance2 = constructor.newInstance();

        System.out.println("Instance 1 hash: " + instance1.hashCode());
        System.out.println("Instance 2 hash: " + instance2.hashCode());
        System.out.println("Are instances equal? " + (instance1 == instance2));
    }
}
 

防止反射破坏的方法:

方法一:在单例类的私有构造函数中添加实例存在性检查。简单有效,但懒汉式多线程下仍有有漏洞。

java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
        // 防止反射破坏单例的可选检查
        if (instance != null) {
            throw new RuntimeException("Attempt to create duplicate singleton instance");
        }
    }

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

方法二:通过双重检查锁定(DCL)实现线程安全的懒汉式单例,并增加反射防护机制

**volatile关键字:**确保多线程环境下instance变量的可见性,防止指令重排序导致的未初始化对象被引用。

**私有构造函数防护:**在构造函数中检查实例是否已存在,若通过反射调用构造函数会直接抛出异常。

**双重检查锁定:**外层判断避免每次获取实例都加锁,内层判断确保多线程环境下只创建一个实例。

java 复制代码
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new IllegalStateException("Singleton instance already exists");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
 
相关推荐
清水白石00811 小时前
Python 编程实战全景:从基础语法到插件架构、异步性能与工程最佳实践
开发语言·python·架构
吴文周11 小时前
告别重复劳动:一套插件让 AI 替你写代码、修Bug、做测试、上生产
前端·后端·ai编程
Cyeam11 小时前
Roadbook CSV:一行 CSV 秒变高德地图路书
后端·开源·aigc
yaoxin52112311 小时前
390. Java IO API - WatchDir 示例
java·前端·python
懒狗小前端12 小时前
做了一个 codex 的中文文档网站,做的不好可以随便喷
前端·后端
Halo_tjn13 小时前
Java 基于字符串相关知识点
java·开发语言·算法
梦想的颜色13 小时前
java 利用redis来限制用户频繁点击
java·开发语言
报错小能手13 小时前
Swift 并发 Combine响应式框架
开发语言·ios·swift
Eric_见嘉13 小时前
在职前端 Agent 配置分享
前端·后端·agent
Ares-Wang13 小时前
Flask》》 Flask-OpenID 认证、 OpenID Connect (OIDC)
后端·python·flask