lengjingbuliaoyidian
文章15
标签7
分类7
单例模式

单例模式

Singleton(单例模式)

  1. 意图:
    确保一个类只有一个实例,并提供一个全局访问点来访问该实例

  2. 注意事项

  • 线程安全:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成实例被多次创建。
  • 延迟初始化:实例在第一次调用 getInstance() 方法时创建。
  • 序列化和反序列化:重写 readResolve 方法以确保反序列化时不会创建新的实例。
  • 反射攻击:在构造函数中添加防护代码,防止通过反射创建新实例。
  • 类加载器问题:注意复杂类加载环境可能导致的多个实例问题。
  1. 结构
    单例模式包含以下几个主要角色:
  • 单例类:包含单例实例的类,通常将构造函数声明为私有。
  • 静态成员变量:用于存储单例实例的静态成员变量。
  • 获取实例方法:静态方法,用于获取单例实例。
  • 私有构造函数:防止外部直接实例化单例类。
  • 线程安全处理:确保在多线程环境下单例实例的创建是安全的。

单例模式的实现

  1. 单例设计模式分类两种:
  • 饿汉式:类加载就会导致该单例实例对象被创建,适用于单线程环境。
  • 懒汉式:类加载不会导致该单例实例对象被创建,而是首次使用该对象时才会创建,适用于多线程环境。

饿汉式——静态成员变量

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 在本类中创建本类对象
private static Singleton instance =new Singleton();

//3. 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}

测试类

public calss Client{
public static void main(String[] args) {
//创建Singleton对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取到的两个对象是否是同一个对象
if(instance==instance1){
System.out.println("两个对象是同一个对象");
}else{
System.out.println("两个对象不是同一个对象");
}
}
}
//结果: 两个对象是同一个对象
//原因: 饿汉式是在类加载时就创建了实例对象,所以在多线程环境下也不会出现多个实例的情况。

饿汉式——静态代码块

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 先申明Singleton对象
private static Singleton instance;//null

//3. 在静态代码块中进行赋值——创建Singleton对象
static{
instance = new Singleton();
}

//3. 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}

饿汉式——枚举

  • 原由:枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会加载一次。而且枚举类型是所用单例实现中唯一不会被破坏的单例实现模式。
public enum Singleton{
INSTANCE;
}

测试类

 public class Client{
public static void main(String[] args){
//创建Singleton对象
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance==instance1);
}
}
// 结果为true,说明是同一个对象
  • 总结:
    优点: 内存中只有一个实例,减少内存开销,尤其是频繁创建和销毁实例时(如管理学院首页页面缓存)。
    避免资源的多重占用(如写文件操作)。
    缺点:饿汉式在类加载时就创建了实例对象,可能造成内存的浪费。

懒汉式——线程不安全情况

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 先申明Singleton对象
private static Singleton instance;//null

//3. 提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
//首先判断instance是否为null,如果为null,说明还没有创建实例对象,则创建
if(instance==null){
//当有两个线程同时调用getInstance()方法时,线程1等待,线程2获取到了cpu的执行权,也会进入判断里面————》可能会创建多个实例对象,因此需要使用同步锁synchronized --》public static synchronized Singleton getInstance()
instance = new Singleton();
}
//如果instance不为null,则直接返回
return instance;
}
}

测试类


public class Client{
public static void main(String[] args){
//创建Singleton对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取到的两个对象是否是同一个对象
System.out.println(instance==instance1);
}
}
//结果为ture

懒汉式——线程安全情况

public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2. 先申明Singleton对象
private static Singleton instance;//null

//3. 提供一个公共的访问方式,让外界获取该对象,同时加上synchronized锁保证线程的安全
public static synchronized Singleton getInstance(){
//首先判断instance是否为null,如果为null,说明还没有创建实例对象,则创建
if(instance==null){

instance = new Singleton();
}
//如果instance不为null,则直接返回
return instance;
}
}

懒汉式——双重检查锁

  • 优势:解决了单例、性能、线程安全问题。因为在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重新排序操作。可以使用 volatile 关键字修饰成员变量和静态成员变量,防止指令重排序。
public class Singleton{
//1. 私有构造方法
private Singleton(){}

//2.声明Singleton对象
public static volatile Singleton instance;

//3.提供一个公共的访问方式,让外界获取该对象
public static Singleton getInstance(){
//第一次判断,如果instance不为null,则直接返回
if(instance==null){
synchronized (Singleton.class){
//第二次判断,如果instance不为null,则直接返回,为空就创建该对象
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}

懒汉式——静态内部类

  • 原由:静态内部类单里模式由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性、方法被调用时才会被加载,并初始化其静态资源,静态资源由于被static修饰,因此只会被创建一次,并且创建时是线程安全的。
public class Singleton{
//1.私有构造方法
private Singleton(){}
//2.定义一个静态内部类
public static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

测试类


public class Client{
public static void main(String[] args){
//创建Singleton对象
Singleton instance = Singleton.getInstance();
Singleton instance1 = Singleton.getInstance();
//判断获取到的两个对象是否是同一个对象
System.out.println(instance==instance1);
}
}
结果为ture
  • 说明:第一次加载Singleton类时并不会初始化instance,只有第一次调用getInstance()方法时,虚拟机加载SingletonHolder类并初始化instance,并且instance是static的,因此只会被创建一次。这样不仅能保证线程的安全性,还能保证Singleton类的唯一性。

存在的问题——破坏单例模式

破会啊单例模式:使上面定义的单例类可以创建多个对象,美剧方式除外,有两种方式:序列化和反射。
例子:

  1. 序列化与反序列化
public class Singleton implements Serializable{
//1.私有构造方法
private Singleton(){}
//2.定义一个静态内部类
public static class SingletonHolder{
//在内部类中声明并初始化外部类的对象
private static final Singleton INSTANCE = new Singleton();
}
//3.提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}

测试类:

public class Client{
public static void main(String[] args) throws Exception {
//writeObject2File(); //执行结果为:C:\\Users\\j.txt 文件创建成功
readObjectFromFile(); //执行结果为:地址1
readObjectF romFile(); //执行结果为:地址2
//说明:序列化和反序列化后的对象不是同一个对象,因此破坏了单例模式。
}
//从文件中读取数据(对象)
public static void readObjectFromFile() throw Exception{
//1.创建输入流对象
ObjectInputStream ois =new ObjectInputStream(new FileInputStream("C:\\Users\\j.txt"));
//2.读取对象
Singleton instance=(Singleton) ois.readObject();
System.out.println(instance);
//3。释放资源
ois.close();
}
//从文件中写数据(对象)
public static void writeObject2File() throws Exception{
//1.获取Singleton对象
Singleton instance = Singleton.getInstance();
//2.创建输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\j.txt"));
//3.写对象
oos.writeObject(instance);
//4.释放资源
oos.close();
}
}
  1. 反射
    public class Client{
    public static void main(String[] args) throws Exception{
    //1.获取Singleton的Class字节码对象
    Class class =Singleton.class;
    //2.获取无参构造方法对象
    Constructor constructor = class.getDeclaredConstructor();
    //3.取消访问检查
    constructor.setAccessible(true);
    //4.通过无参构造方法创建对象
    Singleton instance1 = (Singleton) constructor.newInstance();
    Singleton instance2 = (Singleton) constructor.newInstance();
    //5.判断两个对象是否相等,如果相等,说明反射成功,如果不相等,说明反射失败(破坏了单例模式)
    System.out.println(instance1 == instance2);
    }
    //结果为false
    }

上述问题的解决

1.序列化与反序列化破坏单例模式的解决方法:

  • 在Singleton类中添加readResolve()方法,在序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,反之,就返回新new出来的对象。
    public class Singleton implements Serializable{
    //1.私有构造方法
    private Singleton(){}
    //2.定义一个静态内部类
    public static class SingletonHolder{
    //在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE = new Singleton();
    }
    //3.提供公共的访问方式
    public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
    }
    //4.当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
    public Object readResolve(){
    return SingletonHolder.INSTANCE;
    }
    }

    测试类:
    public class Client{
    public static void main(String[] args) throws Exception {
    //writeObject2File(); //执行结果为:C:\\Users\\j.txt 文件创建成功
    readObjectFromFile(); //执行结果为:地址1
    readObjectF romFile(); //执行结果为:地址1
    //说明:创建对象时,使用的是同一个对象
    }
    //从文件中读取数据(对象)
    public static void readObjectFromFile() throw Exception{
    //1.创建输入流对象
    ObjectInputStream ois =new ObjectInputStream(new FileInputStream("C:\\Users\\j.txt"));
    //2.读取对象
    Singleton instance=(Singleton) ois.readObject();
    System.out.println(instance);
    //3。释放资源
    ois.close();
    }
    //从文件中写数据(对象)
    public static void writeObject2File() throws Exception{
    //1.获取Singleton对象
    Singleton instance = Singleton.getInstance();
    //2.创建输出流对象
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\j.txt"));
    //3.写对象
    oos.writeObject(instance);
    //4.释放资源
    oos.close();
    }
    }
    2.反射破坏单例模式的解决方法:
    public class Singleton {
    private static boolean flag = false;

    //1.私有构造方法
    private Singleton(){

    synchronized (Singleton.class) {
    //判断flag的值是否为true,
    //如果为true,说明已经创建了对象,直接抛出异常
    //如果为false,说明没有创建对象,创建对象
    if (flag) {
    throw new RuntimeException("不能创建多个对象");
    }
    flag = true;
    }
    }

    //2.定义一个静态内部类
    public static class SingletonHolder{
    //在内部类中声明并初始化外部类的对象
    private static final Singleton INSTANCE = new Singleton();
    }

    //3.提供公共的访问方式
    public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
    }
    }

    public class Client{
    public static void main(String[] args) throws Exception{
    //1.获取Singleton的Class字节码对象
    Class class =Singleton.class;
    //2.获取无参构造方法对象
    Constructor constructor = class.getDeclaredConstructor();
    //3.取消访问检查
    constructor.setAccessible(true);
    //4.通过无参构造方法创建对象
    Singleton instance1 = (Singleton) constructor.newInstance(); //正常
    Singleton instance2 = (Singleton) constructor.newInstance(); //抛出异常:不能创建多个对象
    //5.判断两个对象是否相等,如果相等,说明反射成功,如果不相等,说明反射失败(破坏了单例模式)
    System.out.println(instance1 == instance2);
    }

    }
本文作者:冷静不了一点
本文链接:http://example.com/2025/08/06/Java23%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F-%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可