设计模式——单例模式
创始人
2025-05-28 18:14:03
0

单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创
建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供
了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

一、饿汉单例模式

单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

1.静态变量实现饿汉式单例模式

package 单例模式.饿汉式_静态成员变量;
/*** @author Watching* * @date 2023/3/14* * Describe:饿汉模式:静态成员变量方式*/
public class HungerSingleton {//1.私有构造方法private HungerSingleton() {}//2.静态私有成员private static HungerSingleton instance = new HungerSingleton();//3.提供一个静态的方法获取唯一实例public static HungerSingleton getInstance() {return instance;}
}

2.静态代码块实现饿汉式单例模式

package 单例模式.饿汉式_静态代码块;
/*** @author Watching* * @date 2023/3/14* * Describe:饿汉式:静态代码块*/
public class HungerSingleton {//1.私有构造方法private HungerSingleton() {}//2.私有静态成员private static HungerSingleton instance;//3.静态代码块static {instance = new HungerSingleton();}//4.提供公有静态方法访问唯一对象public static HungerSingleton getInstance() {return instance;}
}

3.最简单的饿汉式单例模式——枚举类

1.创建一个枚举类

public enum Singleton {INSTANCE;
}

2.获取这个对象

public class Client {public static void main(String[] args) {Singleton instance1 = Singleton.INSTANCE;Singleton instance2 = Singleton.INSTANCE;System.out.println(instance1 == instance2);}
}

两次获得对象是同一个对象

总结

饿汉式单例模式的三个要点
①私有构造方法
②私有静态成员
③直接初始化静态成员,或者在静态代码块中初始化
④提供静态方法获取单例

二、懒汉单例模式

1.静态内部类实现懒汉单例模式

1.使用静态内部类创建单例类

public class LazySingleton {//1.创建一个私有构造器private LazySingleton() {}//2.创建一个静态内部类,并在其中初始化成员常量(变量也行)static class SingletonHolder {public static final LazySingleton instance = new LazySingleton();}//3.提供方法获取静态内部类中的成员常量(或者变量public static LazySingleton getInstance() {return SingletonHolder.instance;}
}

2.测试

public class Client {public static void main(String[] args) {LazySingleton instance1 = LazySingleton.getInstance();LazySingleton instance2 = LazySingleton.getInstance();System.out.println(instance1 == instance2);}
}

两次获取的对象都是同一个

为何静态内部类中的成员变量是单例的?
因为静态代码块只有在第一次被调用的时候才会加载。

静态内部类实现单例的优点

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。
具体来说当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,
只有当getInstance()方法第一次被调用时,使用INSTANCE的时候,才会导致虚拟机加载SingleTonHoler类。
这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

那么他是如何实现线程安全的?
首先要了解类加载过程中的最后一个阶段:即类的初始化,类的初始化阶本质就是执行类构造器的方法。
方法:这不是由程序员写的程序,而是根据代码由javac编译器生成的。
它是由类里面所有的类变量的赋值动作和静态代码块组成的。
JVM内部会保证一个类的方法在多线程环境下被正确的加锁同步,也就是说如果多个线程同时去进行“类的初始化”,
那么只有一个线程会去执行类的方法,其他的线程都要阻塞等待,直到这个线程执行完方法。
然后执行完方法后,其他线程唤醒,但是不会再进入()方法。
也就是说同一个加载器下,一个类型只会初始化一次。

2.同步锁实现懒汉单例模式

为了保证懒汉式在多线程的环境中是线程安全的,只需要在基本的懒汉式的方法签名上加synchronized同步锁

public class LazySingleton {private LazySingleton() {}private static LazySingleton instance;//只需要在方法签名上添加一个synchronized关键字就可以保证该方法的线程安全性public static synchronized LazySingleton getInstance() {//判断instance是否为null 为null才创建,保证唯一性if (instance == null) {instance = new LazySingleton();}return instance;}
}

这样可以保证并发中懒汉单例模式的线程安全,但是由于整个方法都被同步锁锁住,导致方法效率变低,每个线程的每次请求都会阻塞等待上一个线程判断是否存在单例。

3.双重检查锁实现懒汉式单例※

使用同步锁实现懒汉式单例模式导致性能下降,为了避免,我们是可以使用双重检查锁实现懒汉式单例模式。

public class LazySingleton {private LazySingleton() {}// 1.添加volatile关键字后可以保证instance的有序性,不添加volatile关键字可能会出现空指针异常(由于jvm的指令重排)private static volatile LazySingleton instance;public static  LazySingleton getInstance() {//2.判断instance是否为null 为null就锁住后创建实例,保证安全性,双重检查锁解决了同步锁导致的多个线程进入需要排队取锁而影响性能的问题if (instance == null) {synchronized (LazySingleton.class){if(instance == null){instance = new LazySingleton();}}}return instance;}
}

实现思路:是当一个线程已经进入instance = new LazySingleton();语句后,其他线程就要等待,如果当一个线程已经成功创建单例后,其余的线程进入就只会在第一条if语句处跳出,而不需要等待锁的释放。且之后的每条线程都会直接从第一个if语句处跳出,从而保证了线程安全,大大的提高了性能。
注意点:需要添加volatile关键字修饰静态成员变量,防止空指针异常。(详情请了解jvm的指令重排)

三、破坏单例模式与预防方法

1.反射破坏单例模式

思路:通过反射获取单例类的构造器,通过构造器创建实例

public class Client {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {//1.获取单例类的类对象Class hungerSingletonClass = HungerSingleton.class;//2.获取单例类的构造器并修改访问权限Constructor constructor = hungerSingletonClass.getDeclaredConstructor();constructor.setAccessible(true);//设置访问权限,可以访问私有构造方法//3.使用获取到的构造器创建实例HungerSingleton hungerSingleton1 = constructor.newInstance();HungerSingleton hungerSingleton2 = constructor.newInstance();//4.测试发现两个实例不是同一个对象,单例被破坏了。System.out.println(hungerSingleton1.toString());System.out.println(hungerSingleton2.toString());/*** 两次对象结果的地址不同,说明他们不是同一个对象,单例被破坏了*/}
}

预防方法

在构造方法中判断是否已经创建单例,如果已经创建,则直接手动抛出异常

public class HungerSingleton {//提供一个flag用于判断对象是否已经创建private static boolean flag = false;//1.私有构造方法private HungerSingleton() {//加锁保证多线程安全问题synchronized (HungerSingleton.class){//在无参构造中提供一个判断,如果已经创建了对象则抛出异常if(flag){throw new RuntimeException("禁止创建多个对象");}flag = true;}}//2.私有静态成员private static HungerSingleton instance;//3.静态代码块static {instance = new HungerSingleton();}//4.提供公有静态方法访问唯一对象public static HungerSingleton getInstance() {return instance;}
}

使用一个boolean类型的flag初始化为false,当flag为true是说明已经存在单例,再通过构造器创建实例就直接抛出异常。
第一次调用构造器即第一次创建单例会将flag置为true
测试一下
在这里插入图片描述
发现想利用反射创建两个实例,直接抛出了我们设置的异常。

2.反序列化破坏单例与预防方法

思路:将单例类序列化通过流存储在文件中,再通过反序列化将文件中的数据生成对象

public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObjectToFile(HungerSingleton.getInstance());readObjectFromFile();readObjectFromFile();/*** 输出结果显示两个对象的地址不同,即代表两个对象不是同一个,这就破坏了单例模式了*/}//将对象序列化并存入文件public static void writeObjectToFile(HungerSingleton hungerSingleton) throws IOException {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("D:\\d.txt")));oos.writeObject(hungerSingleton);oos.close();}//将文件中的内容反序列化为对象public static void readObjectFromFile() throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D:\\d.txt")));HungerSingleton hungerSingleton = (HungerSingleton) ois.readObject();System.out.println(hungerSingleton.toString());}
}

此时两次反序列化生成的对象就是不同的两个对象。
注意:被序列化的类需要实现Serializable接口

预防方法

在单例类中添加一个readResolve()方法,在该方法内调用获取单例的静态方法

public class HungerSingleton implements Serializable {//1.私有构造方法private HungerSingleton() {}//2.静态私有成员private static HungerSingleton instance = new HungerSingleton();//3.提供一个静态的方法获取唯一实例public static HungerSingleton getInstance() {return instance;}//4.提供readResolve()方法,保证反序列化为同一个对象public Object readResolve(){return HungerSingleton.getInstance();}
}

ObjectInputStream 类中,会做一个判断,判断序列化的类中是否存在 readResolve() 方法,如果有,则会在反序列化的时候调用 readResolve() 方法。
有兴趣可以查看 ObjectInputStream 源码第2243行

四、jdk源码解析

在JDK中,Runtime类也是使用了饿汉式单例模式

public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class Runtime are instance* methods and must be invoked with respect to the current runtime object.** @return  the Runtime object associated with the current*          Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}.............
}

可以看到Runtime也初始化了一个静态成员变量,和一个静态方法获取该成员变量,并且只有一个私有构造方法

相关内容

热门资讯

扫房神器2安卓系统,打造洁净家... 你有没有发现,家里的灰尘就像小精灵一样,总是悄悄地在你不注意的时候跳出来?别急,今天我要给你介绍一个...
安卓完整的系统设置,全面掌控手... 亲爱的手机控们,是不是觉得你的安卓手机用久了,功能越来越强大,但设置却越来越复杂?别急,今天就来带你...
电视安卓系统是几代机子,揭秘新... 你有没有想过,家里的电视是不是已经升级到了最新的安卓系统呢?别小看了这个小小的系统升级,它可是能让你...
安卓系统隐私有经常去,系统级防... 你知道吗?在咱们这个数字化时代,手机可是我们生活中不可或缺的好伙伴。但是,你知道吗?这个好伙伴有时候...
安卓10系统断网软件,轻松实现... 你有没有遇到过这种情况?手机突然断网了,明明信号满格,却连不上网,急得你团团转。别急,今天就来给你揭...
安卓可以改什么系统版本,体验全... 你有没有想过,你的安卓手机其实可以像换衣服一样,换一个全新的“系统版本”呢?没错,这就是今天我们要聊...
最好的平板游戏安卓系统,畅享指... 亲爱的游戏迷们,你是否在寻找一款能够让你在安卓平板上畅玩无忧的游戏神器?别急,今天我就要给你揭秘,究...
华为安卓系统卡顿解决,华为安卓... 你是不是也遇到了华为安卓系统卡顿的问题?别急,今天就来给你支几招,让你的华为手机重新焕发活力!一、清...
安卓建议升级鸿蒙系统吗,探讨鸿... 亲爱的安卓用户们,最近是不是被鸿蒙系统的新鲜劲儿给吸引了?是不是在犹豫要不要把你的安卓手机升级成鸿蒙...
安卓如何变苹果系统桌面,桌面系... 你有没有想过,把你的安卓手机变成苹果系统桌面,是不是瞬间高大上了呢?想象那流畅的动画效果,那简洁的界...
windows平板安卓系统升级... 你有没有发现,最近你的Windows平板电脑突然变得有些不一样了?没错,就是那个一直默默陪伴你的小家...
安卓系统扩大运行内存,解锁更大... 你知道吗?在科技飞速发展的今天,手机已经成为了我们生活中不可或缺的好伙伴。而手机中,安卓系统更是以其...
安卓系统怎么改变zenly,探... 你有没有发现,你的安卓手机上的Zenly应用最近好像变得不一样了?没错,安卓系统的大手笔更新,让Ze...
英特尔安卓子系统,引领高效移动... 你有没有想过,手机里的安卓系统竟然也能和电脑上的英特尔处理器完美结合呢?这可不是天方夜谭,而是科技发...
永远会用安卓系统的手机,探索安... 亲爱的手机控们,你是否也有那么一款手机,它陪伴你度过了无数个日夜,成为了你生活中不可或缺的一部分?没...
有哪些安卓手机系统好用,好用系... 你有没有发现,现在手机市场上安卓手机的品牌和型号真是琳琅满目,让人挑花了眼?不过别急,今天我就来给你...
卡片记账安卓系统有吗,便捷财务... 你有没有想过,用手机记账是不是比拿着小本本记录来得方便多了?现在,手机上的应用层出不穷,那么,有没有...
武汉摩尔影城安卓系统APP,便... 你有没有想过,一部手机就能带你走进电影的世界,享受大屏幕带来的震撼?今天,就让我带你详细了解武汉摩尔...
联想刷安卓p系统,畅享智能新体... 你有没有发现,最近联想的安卓P系统刷机热潮可是席卷了整个互联网圈呢!这不,我就迫不及待地来和你聊聊这...
mac从安卓系统改成双系统,双... 你有没有想过,你的Mac电脑从安卓系统改成双系统后,生活会有哪些翻天覆地的变化呢?想象一边是流畅的苹...