单例模式

单例模式的意义:

有一些对象我们只需要一个实例,比如说线程池、缓存、对话框、日志对象、重放打印机、显卡等设备的驱动程序。这些对象只能有一个实例,否则会产生很多问题。
为了确保只有一个实例,有时我们通过全局变量的形式实现,但是将对象赋值给全局变量,但是却没有使用就会造成资源的浪费。所以还只实例化一个实例更好。

总所周知,类对象的构造函默认是public类型的,这样我就是说这个对象可以有很多实例。当然我们构造函数虽然不是公有的类型(如protected),只有同一个包的类可以实例化它时,但是仍可以实例化多次,
私有的构造函数不能被类外实例化,只能类内部实例化。但很显然不能够通过类的实例来调用构造器,因为类实例的产生和构造器的调用就像"鸡生蛋,蛋生鸡"一样, 谁先谁后说不清楚。但是如果通过类用却是可以的。可以通过调用该类的静态方法,再通过静态方法调用私有的构造函数。
单例模式确保一个类只有一个实例,并提供一个全局的访问点。

单例模式的种类:

简单懒汉式

一个简单的单例模式的实现如下:

复制代码
public class LazySingleton {
    //利用一个静态变量记录LazySingleton的唯一实例
    private static LazySingleton uniqueInstance;

    //把构造器声明为私有的,只有自LazySingleton类内才可以调用构造器
    private LazySingleton(){}

    //用getinstance方法实例化对象,并返回这个实例。
    public static LazySingleton singletonGetInstance() {
        if(uniqueInstance != null){
            //需要的时候就产生实例
            uniqueInstance = new LazySingleton();
        }else{
            System.out.println("already exists lazySingleton instance");
        }
        return uniqueInstance;
    }
}
复制代码

通过懒汉模式获取单例在多线程的情况是不安全的,我们可以通过对获取实例的方式进行加锁。

尽管如此,这种方式只有第一次执行的时侯,才真正需要同步。一旦设置好uniqueInstance这个变量,就不再需要同步这个方法了以后的每次调用都会显得累赘。

升级后的懒汉式

当然如果getInstanced()的性能对应用程序不是很关键,可以什么都不做。但是要考虑加了同步的方法性能可能下降100倍那么getInstance的方法被频繁使用,可能需要慎重考虑了。
当然我们可以把同步的地方换到方法内部,如下所示:

复制代码
public class UpdatedLazySingleton {
    //volatile关键词确保,当uniqueInstance变量被初始化成实例时,多个线程正确地处理uniqueInstance变量
    private volatile static UpdatedLazySingleton uniqueInstance;

    private UpdatedLazySingleton(){}

    public static UpdatedLazySingleton getInstance() {
        //检查实例,如果不存在则进入同步区块
        if(uniqueInstance == null){
            //只有第一次才彻底执行这里的代码,当然这里可能有多个线程在这里等待,所以需要在同步块内部再次判断单例变量是否为空。
            synchronized (UpdatedLazySingleton.class) {
                if(uniqueInstance == null){
                    //进入区块后,再检查一次,如果仍是null,才创建实例
                    uniqueInstance = new UpdatedLazySingleton();
                }
            }
        }
        return uniqueInstance;
    }
}
复制代码

这种双重if检查加锁的实现方式可以大大减少getInstance()的时间消耗。

饿汉式

如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,可能需要急切创建此单例:

复制代码
public class HungrySingleton {
    private static HungrySingleton uniqueInstance = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return uniqueInstance;
    }
}
复制代码

这种做法依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。虽然这种做法在性能上比较号,但是如果这个单例没有被使用,那么会占用资源。

静态类优化的饿汉式

饿汉模式可以通过静态内部类进行优化,代码如下:

复制代码
class StaticInnerSingleton {
    private static class HolderClass{
        //默认是不加载的
        private static final StaticInnerSingleton instance = new StaticInnerSingleton();
    }

    public static StaticInnerSingleton getInstance(){
        //返回结果之前一定先会先加载内部类
        return HolderClass.instance;
    }
}
复制代码

枚举单例

复制代码
public class EnumSingleton {
    private EnumSingleton() {
        System.out.println("+++++");
    }
    public static EnumSingleton getInstance() {
        return EnumHolder.INSTANCE.getSingleton();
    }

    //使用枚举充当holder
    private enum EnumHolder {
        INSTANCE;
        private EnumSingleton instance;

        EnumHolder() {
            this.instance = new EnumSingleton();
        }

        private EnumSingleton getSingleton() {
            return instance;
        }
    }
}
复制代码

破环单例:

反射破坏单例:

复制代码
import org.junit.Test;

import java.lang.reflect.Constructor;

public class ReflectionDestroySingleton {
    @Test
    public void destroyHungrySingleton(){
        Class<?> clazz = HungrySingleton.class;
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            HungrySingleton singleton = (HungrySingleton)constructor.newInstance();
            HungrySingleton singleton2 = (HungrySingleton)constructor.newInstance();
            System.out.println("反射破坏饿汉模式");
            System.out.println(singleton == singleton2);
            System.out.println(singleton);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void destroyStaticInnerSingleton(){
        Class<?> clazz = StaticInnerSingleton.class;
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            StaticInnerSingleton singleton = (StaticInnerSingleton)constructor.newInstance();
            StaticInnerSingleton singleton2 = (StaticInnerSingleton)constructor.newInstance();
            System.out.println("反射破坏静态内部类实现的饿汉模式");
            System.out.println(singleton == singleton2);
            System.out.println(singleton);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void destroyUpdatedLazySingleton(){
        Class<?> clazz = UpdatedLazySingleton.class;
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            UpdatedLazySingleton singleton = (UpdatedLazySingleton)constructor.newInstance();
            UpdatedLazySingleton singleton2 = (UpdatedLazySingleton)constructor.newInstance();
            System.out.println("反射破坏升级后的懒汉模式");
            System.out.println(singleton == singleton2);
            System.out.println(singleton);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void destroyLazySingleton(){
        Class<?> clazz = LazySingleton.class;
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            LazySingleton singleton = (LazySingleton)constructor.newInstance();
            LazySingleton singleton2 = (LazySingleton)constructor.newInstance();
            System.out.println("反射破坏升级后的懒汉模式");
            System.out.println(singleton == singleton2);
            System.out.println(singleton);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void destroyEnumSingleton(){
        Class<?> clazz = EnumSingleton.class;
        try {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            EnumSingleton singleton = (EnumSingleton)constructor.newInstance();
            EnumSingleton singleton2 = (EnumSingleton)constructor.newInstance();
            System.out.println("反射破坏枚举单例");
            System.out.println(singleton == singleton2);
            System.out.println(singleton);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

执行结果:

复制代码
反射破坏升级后的懒汉模式
false
headFirst.singleton.LazySingleton@2b71fc7e
headFirst.singleton.LazySingleton@5ce65a89
反射破坏饿汉模式
false
headFirst.singleton.HungrySingleton@69d0a921
headFirst.singleton.HungrySingleton@446cdf90
反射破坏静态内部类实现的饿汉模式
false
headFirst.singleton.StaticInnerSingleton@4b85612c
headFirst.singleton.StaticInnerSingleton@277050dc
反射破坏升级后的懒汉模式
false
headFirst.singleton.UpdatedLazySingleton@7aec35a
headFirst.singleton.UpdatedLazySingleton@67424e82
反射破坏枚举单例
java.lang.NoSuchMethodException: headFirst.singleton.EnumSingleton.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getDeclaredConstructor(Class.java:2178)
    at headFirst.singleton.ReflectionDestroySingleton.destroyEnumSingleton(ReflectionDestroySingleton.java:81)
复制代码

序列化破环单例:

复制代码
public class SerializableDestroySingleton {
    @Test
    public void destroyLazySingleton(){
        LazySingleton lazySingleton = null;
        LazySingleton lazySingleton2 = LazySingleton.getInstance();
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("LazySingleton.obj");
            ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
            out.writeObject(lazySingleton2);
            out.flush();
            fileOutputStream.close();

            FileInputStream fileInputStream = new FileInputStream("LazySingleton.obj");
            ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
            Object o = inputStream.readObject();
            lazySingleton = (LazySingleton)o;

            System.out.println(lazySingleton == lazySingleton2);
            System.out.println(lazySingleton);
            System.out.println(lazySingleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
复制代码

以单例破坏和序列化破坏为例,简述预防措施:

复制代码
public class LazySingleton implements Serializable{
    //利用一个静态变量记录LazySingleton的唯一实例
    private static LazySingleton uniqueInstance;

    //把构造器声明为私有的,只有自LazySingleton类内才可以调用构造器
    private LazySingleton() {
        //防止反射创建不同的单例对象
        throw new RuntimeException("非法创建对象");
    }

    //用getinstance方法实例化对象,并返回这个实例。
    public static LazySingleton getInstance() {
        if (uniqueInstance != null) {
            //需要的时候就产生实例
            uniqueInstance = new LazySingleton();
        } else {
            System.out.println("already exists lazySingleton instance");
        }
        return uniqueInstance;
    }
     
    private Object readResolve(){
        return uniqueInstance;
    }
}
复制代码

 

public class SerializableDestroySingleton {
@Test
public void destroyLazySingleton(){
LazySingleton lazySingleton = null;
LazySingleton lazySingleton2 = LazySingleton.getInstance();
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream("LazySingleton.obj");
ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
out.writeObject(lazySingleton2);
out.flush();
fileOutputStream.close();

FileInputStream fileInputStream = new FileInputStream("LazySingleton.obj");
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
Object o = inputStream.readObject();
lazySingleton = (LazySingleton)o;

System.out.println(lazySingleton == lazySingleton2);
System.out.println(lazySingleton);
System.out.println(lazySingleton2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
posted @   小兵要进步  阅读(37)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)

侧边栏
点击右上角即可分享
微信分享提示