保证一个类仅有一个实例,并提供一个访问它的全局访问点。
别名:单件模式
【开发】:老大,为什么我保存配置信息,每次都和我预期的不一样啊,总是会覆盖?
【BOSS】:哈?我来看看。
【BOSS】:你每次使用的时候都会new一个新的配置对象吗?
【开发】:对啊,有什么问题?
【BOSS】:那肯定不对啊,像这种配置信息,全局只应该有一个,不然会互相影响!
public class HazardousTypeSingleton { private static final App APP = new App(); // 私有构造方法 private HazardousTypeSingleton () {} // 类加载时已初始化,不会有多线程的问题 static App getInstance() { System.out.println("APP - 饿汉型模式"); return APP; } } 复制代码
❝名字由来:因为随着类加载而加载,显得很“急迫”,所以称之为饿汉型
❞
**评价:**这样的写法和全局变量没有本质的区别,不推荐
public class LazyTypeSingleton { private LazyTypeSingleton () {} // 静态私用成员,没有初始化 private static App intance = null; /*** * 直接加synchronized关键字 */ synchronized static App getIntance () { System.out.println("APP - 懒汉型模式"); if (null == intance) { intance = new App(); return intance; } return intance; } } 复制代码
❝名字由来:调用时才加载,因此称之为懒汉型
❞
**评价:**这样写有延迟加载的功能,但是加了一个synchronized大锁,因此多线程环境下效率较低
public class LazyTypeSingleton { // volatile关键字修饰,防止指令重排 private volatile static App app = null; /*** * Double Check Lock(DCL) 双重锁校验 */ static App getInstanceByDCL () { if (null == app) { synchronized (LazyTypeSingleton.class) { if (null == app) { System.out.println("APP - 饿汉模式DCL 双重锁校验"); app = new App(); return app; } } } return app; } } 复制代码
❝注意volatile关键字起到的作用,详情请见:https://juejin.im/post/5ebadd9df265da7bda414c20
❞
**评价:**比较推荐的写法,可以保证线程安全,同时具备延时加载的效果
public class InnterTypeSingleton { private InnterTypeSingleton(){ throw new IllegalStateException(); } // 静态内部类方式,类似饿汉保证天然的线程安全 private static class SingletonHolder{ private final static App app = new App(); } static App getInstance(){ System.out.println("APP - 静态内部类方式(Holder)"); return SingletonHolder.app; } } 复制代码
**评价:**线程安全,调用效率高,可以延时加载
public class InnterTypeSingletonError { private InnterTypeSingletonError(){ System.out.println(5 / 0); } private static class SingletonHolder{ private final static InnterTypeSingletonError app = new InnterTypeSingletonError(); } static InnterTypeSingletonError getInstance(){ System.out.println("APP - 静态内部类方式(Holder)"); return SingletonHolder.app; } public static void main(String[] args){ try { InnterTypeSingletonError.getInstance(); } catch (Throwable t) { t.printStackTrace(); } try { InnterTypeSingletonError.getInstance(); } catch (Throwable t) { t.printStackTrace(); } } } 复制代码
注意看上文中代码块:
private InnterTypeSingletonError(){ System.out.println(5 / 0); } 复制代码
这样一定会出错,运行结果报错信息如下:
APP - 静态内部类方式(Holder) APP - 静态内部类方式(Holder) java.lang.ExceptionInInitializerError at com.design.singleton.InnterTypeSingletonError.getInstance(InnterTypeSingletonError.java:23) at com.design.singleton.InnterTypeSingletonError.main(InnterTypeSingletonError.java:28) Caused by: java.lang.ArithmeticException: / by zero at com.design.singleton.InnterTypeSingletonError.<init>(InnterTypeSingletonError.java:14) at com.design.singleton.InnterTypeSingletonError.<init>(InnterTypeSingletonError.java:11) at com.design.singleton.InnterTypeSingletonError$SingletonHolder.<clinit>(InnterTypeSingletonError.java:18) ... 2 more java.lang.NoClassDefFoundError: Could not initialize class com.design.singleton.InnterTypeSingletonError$SingletonHolder at com.design.singleton.InnterTypeSingletonError.getInstance(InnterTypeSingletonError.java:23) at com.design.singleton.InnterTypeSingletonError.main(InnterTypeSingletonError.java:34) 复制代码复制代码
可以发现它第一次报错是正常的异常,第二次如果再报错就是Could not initialize class ,为什么呢?
因为:「类加载时静态变量只会在第一次加载时,进行初始化,此后不管成不成功,都不会进行第二次初始化了」
所以使用的时候需要注意
public enum EnumSingleton { /*** * APP对象 */ APP; private App app; EnumSingleton() { app = new App(); } public App getInstance() { System.out.println("**************************"); System.out.println("APP - 枚举方式"); return app; } } 复制代码
**评价:**线程安全,调用效率高,不能延时加载,可以天然的防止反射和反序列化调用
在以下情况可以使用单例模式:
在很多项目中的数据库连接池,亦或是配置中心,配置文件对象等等,非常常见~
感谢Java3Y的文章:三歪写Bug写哭了,从中学习到了内部类使用时的神器报错
单例模式使用的场景其实固化,任何需要单一对象工作时的场景都可以使用单例模式,同时只推荐以下三种写法:
GitHub地址
break-word; color: #664D9D; font-weight: normal; border-bottom: 1px solid #664D9D;">mdnice 排版