单例模式
单例模式的七种写法
Ensure a class has only one instance,and provide a global point of access to it.
确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例**
优点:
- 单例模式内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建、销毁时,
而且创建或销毁的性能又无法优化。单例的优势就很明显了 - 减少了系统的性能开销,当一个对象产生较多资源时,或者读取配置时,可以在启动的时候,
生成一个对象,让它永驻内存来解决。 - 可以避免一个对资源的多重占用,比如写文件,由于只有一个实例,避免了对一个文件同时写操作
- 可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,
负责所有数据表的映射处理
缺点:
- 单例模式一般没有接口,扩展很困难,若要扩展的话,只有修改源代码,为什么不设置接口,
因为单例要自行实例化,接口或抽象类是不能被实例化的。 - 单例模式对测试是不利的,在并行环境中,如果单例模式没有完成,是不能进行测试的。
- 单例模式与单一职责原则有冲突,一个类应该只有一个实现逻辑,而不关心它是否是是单例的,
是不是单例要取决于环境,单例模式吧“要单例”和业务逻辑融合在一个类中。
使用场景:
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象的话,就会出现“不良反应”,
可以采用单例模式。
- 要求生成为唯一***的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面的计数器,可以不用把值写入到数据库,
利用单例模式保存 - 创建一个对象需要消耗过多的资源,比如访问I/O或者数据库
- 需要定义大量的静态常量,和静态方法(如工具类)等。
写法:
写法一:饱汉模式
/**
* 饱汉模式,核心是懒加载,好处是启动快,节约资源,一直到实例被第一次访问的时候,才初始化。
* 坏处主要是线程不安全。if存在竟态条件
*/
public class Singleton1 {
public static Singleton1 singleton1=null;
private Singleton1(){
}
public static Singleton1 getInstance(){
//if存在竟态条件,当两个线程同时执行道这里的时候就会出错
if (singleton1==null){
singleton1=new Singleton1();
}
return singleton1;
}
}
写法二:饱汉模式变种一
/**
* 饱汉模式变种1,添加Synchronized修饰,则线程安全,坏处是并发性能差
*/
public class Singleton1_1 {
public static Singleton1_1 singleton1_1=null;
private Singleton1_1(){
}
public synchronized static Singleton1_1 getInstance(){
if (singleton1_1==null){
singleton1_1=new Singleton1_1();
}
return singleton1_1;
}
}
写法三: 饱汉模式DCL 1.0
/**
* 饱汉模式变种2 DCL 1.0 在变种1的基础上又套了一次check,Double Check Lock DCL
* 似乎达到了懒加载+线程安全,但是由于指令重排序,有可能得到是半个对象
*/
public class Singleton1_2 {
public static Singleton1_2 singleton1_2=null;
private Singleton1_2(){
}
public static Singleton1_2 getInstance(){
if (singleton1_2==null){
synchronized (Singleton1_2.class){
if (singleton1_2==null){
singleton1_2=new Singleton1_2();
}
}
}
return singleton1_2;
}
}
写法四:饱汉模式 DCL 2.0
/**
* 饱汉模式变种2 DCL 2.0 在变种1_2上加上一个volatile
*/
public class Singleton1_3 {
public static volatile Singleton1_3 singleton1_3=null;
private Singleton1_3(){
}
public static Singleton1_3 getInstance(){
if (singleton1_3==null){
synchronized (Singleton1_3.class){
if (singleton1_3==null){
singleton1_3=new Singleton1_3();
}
}
}
return singleton1_3;
}
}
写法五:饿汉模式
/**
* 饿汉模式,好处是线程安全,使用时没有延迟,坏处是可能造成资源浪费。
*/
public class Singleton2 {
public static final Singleton2 singleton2=new Singleton2();
private Singleton2(){
}
public static Singleton2 getInstance(){
return singleton2;
}
}
写法六:Holder模式
/**
* Holder模式,希望利用饿汉模式中静态变量的方便和线程安全,又希望通过懒加载规避资源浪费,
* Holder模式满足了这两点需求,核心仍是静态变量,足够方便和安全,通过静态的Holder类持有真正实例
* 间接满足了懒加载。
*/
public class Singleton3 {
// 通过静态的内部类持有真正实例
private static class SingletonHolder{
private static final Singleton3 singleton3=new Singleton3();
private SingletonHolder(){
}
}
private Singleton3(){
}
public static Singleton3 getInstance(){
// 通过Holder来懒加载
return SingletonHolder.singleton3;
}
}
写法七:枚举模式
/**
* 丑类但好用的语法糖,通过jsd反编译,得到的与饿汉模式相同,不过区别在于
* 枚举的方式是公用的静态变量。
*/
// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
void doSomething();
}
public enum Singleton implements MySingleton {
INSTANCE {
@Override
public void doSomething() {
System.out.println("complete singleton");
}
};
public static MySingleton getInstance() {
return Singleton.INSTANCE;
}
}
或者
public enum Singleton4 {
SINGLETON_4;
private SingletonExample singleton;
//JVM保证这个方法绝对只执行一次
Singleton4{
singleton=new SingletonExample();
}
public SingletonExample getInstance(){
return singleton;
}
}
//调用方式
public class SingletonExample{
private SingletonExample(){}
public static SingletonExample getInstance(){
return Singleton4.SINGLETON_4.getInstance();
}
}
通过反编译(jad,源码|String拼接操作”+”的优化?也用到了)打开语法糖,就看到了枚举类型的本质,简化如下:
// 枚举
// ThreadSafe
public class Singleton4 extends Enum<Singleton4> {
...
public static final Singleton4 SINGLETON = new Singleton4();
...
}
本质上和饿汉模式相同,区别仅在于公有的静态成员变量。
实现方式 | 关键点 | 资源浪费 | 线程安全 | 多线程环境的性能足够优化 |
---|---|---|---|---|
基础饱汉 | 懒加载 | 否 | 否 | - |
饱汉变种1 | 懒加载、同步 | 否 | 是 | 否 |
饱汉变种2 | 懒加载、DCL | 否 | 否 | - |
饱汉变种3 | 懒加载、DCL、volatile | 否 | 是 | 是 |
饿汉 | 静态变量初始化 | 是 | 是 | 是 |
Holder | 静态变量初始化、holder | 否 | 是 | 是 |
枚举 | 枚举本质、静态变量初始化 | 否 | 是 | 是 |
通过以上两篇文章,让我对单例模式有了更加深入的理解,收集整理一波,以便后面回顾。
安全性问题
原来没有考虑过单例模式的安全性问题,但其实通过反射是可以破坏它的单例的特性的
一个饿汉模式:
public class Singleton {
public static Singleton singleton=new Singleton();
private Singleton(){}
public static Singleton getSingleton() {
return singleton;
}
}
下面通过反射代码测试:
public class Main {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Singleton singleton=Singleton.getSingleton();
Constructor constructor=Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton1= (Singleton) constructor.newInstance();
System.out.println(singleton == singleton1);
}
}
用枚举解决
参考文章:《设计模式之禅》—秦小波著 和 猴子007 的文章 面试中单例模式有几种写法?