介绍
java 面试题中,手写单例模式 是一个命中率比较高的面试题。因此,我们必须得熟练掌握它。其实它并不难,就看你有没有掌握其中的原理。 其实在我刚接触java 编程的时候,我更多的是死记硬背这个单例模式。每次面试的时候都要背一遍。现在想起来,很是尴尬。如果有对这个单例模式还有不清楚的地方,希望本博客能帮助到大家。
几种常见的实现方式
饿汉模式
原理
因为在类加载完成的时候就实现了初始化,是虚拟机完成的,根本不存在多线程的问题。此时都还没有多线程竞争现象。
优点
代码编写简单,比较好理解。速度快。
缺点
如果是比较大的对象,会从类加载的时候一直占据服务器内存。可能这个对象在服务器运行很长时间后才会需要创建。那么这段时间就是白白占据着内存。并且反射 可以暴力破解 私有构造方法。
代码展示
/**
* @author :echo_黄诗
* @description:饿汉模式
* @date :2023/2/6 16:40
*/
public class Hungry {
//静态熟悉,类加载时初始化,避免多线程初始化的问题
private static Hungry hungry=new Hungry();
//私有构造
private Hungry(){}
//公共方法获取实例
public static Hungry getHungryEntity(){
return hungry;
}
/**
* 50 个线程模拟 多线程环境
* @param args
*/
public static void main(String[] args) {
for (int i=0;i<50;i++){
Thread thread=new Thread(){
@Override
public void run() {
System.out.println("获取单例实例: "+getHungryEntity());
}
};
thread.start();
}
}
}
截图验证
懒汉模式
原理
在客户端调用的时候才初始化。采用同步代码块的方式,保证只初始化一个实例
优点
和饿汉模式相比,它只有在调用的时候才会被加载。服务器的性能会相对的有所提升。
缺点
代码稍微优点复杂,对程序员的要求有所提高,对类加载和并发知识有要求。并且反射 可以暴力破解 私有构造方法。
代码
/**
* @author :echo_黄诗
* @description:懒汉模式 调用的时候才会初始化
* @date :2023/2/6 16:45
*/
public class LazySingle {
private static Object lock=new Object();
private LazySingle() {
}
//禁止指令重排
private volatile static LazySingle lazySingle = null;
public static LazySingle getSingleInstance() {
if (lazySingle == null) {
//此时有多个线程进来 ,比如线程A B
synchronized (lock) {
//此时lazySingle 可能已经被初始化了,需要重新判断下
// 如果A 线程进来初始化了,B 进来后就不需要初始化了,所以需要再次判断
// 使用 volatile 关键字原因 初始化步骤不是原子性的。
// 分为三步 第一步 分配内存空间
// 第二步 初始化
// 第三步 将内存首地址赋值给变量
// 比如第三步和第一步完成了,第二步比较耗时,还没有完成。
// lazySingle == null 这个判断为true. 此时直接返回对象,返回的是一个不完全的对象。
// volatile 关键字保证,第三步完成之前,第二步必须要完成,不能指令重排
if (lazySingle == null) {
lazySingle = new LazySingle();
}
}
}
return lazySingle;
}
/**
* 测试
* @param args
*/
public static void main(String[] args) {
for (int i=0;i<50;i++){
Thread thread=new Thread(){
@Override
public void run() {
System.out.println("获取单例实例: "+getSingleInstance());
}
};
thread.start();
}
}
}
枚举模式
Google 首席 Java 架构师、《Effective Java》一书作者、Java集合框架的开创者Joshua Bloch在Effective Java一书中提到:
单元素的枚举类型已经成为实现Singleton的最佳方法。
原理
Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。 由虚拟机保证。
优点
既可以避免多线程同步问题;还可以防止通过反射和反序列化来重新创建新的对象。在很多优秀的开源代码中,我们经常可以看到使用枚举方式来实现的单例模式类。
缺点
可能对枚举不熟悉是它的缺点了。
代码
/**
* @author :echo_黄诗
* @description:枚举实现 单例
* @date :2023/2/6 17:11
*/
public class EnumSingleton {
private EnumSingleton(){}
static enum SingletonEnum{
//创建一个枚举对象,为单例对象
INSTANCE;
private EnumSingleton enumSingleton;
private SingletonEnum(){
enumSingleton=new EnumSingleton();
}
public EnumSingleton getInstance(){
return enumSingleton;
}
}
public static EnumSingleton getInstance(){
return SingletonEnum.INSTANCE.getInstance();
}
public static void main(String[] args) {
for (int i=0;i<50;i++){
Thread thread=new Thread(){
@Override
public void run() {
System.out.println("获取单例实例: "+getInstance());
}
};
thread.start();
}
}
}
测试验证
总结
简单介绍了三种实现方式。饿汉模式,懒汉模式和枚举方式。建议使用 枚举方式。如果大家有什么建议,欢迎讨论。