单例模式
定义:
单例模式确保该类只能有一个创建的实例,保证全局的唯一性,可以直接被访问,但是不能被创建。
特点:
- 只有一个实例,唯一性
- 单例类不提供创建方法,只能自己创建唯一的实例
- 提供公开的方法,向所有对象提供这一个实例
通用理解:
创建:
public class SingleObj{
//创建 自己 对象
private static SingleObj instance = new SingleObj();
//构造函数为private,所以只能自己实例化
private SingleObj(){}
//公开实例的唯一对象
public static SingleObj getInstance(){
return instance;
}
public void showMessage(){
System.out.println(“hello world!”);
}
}
使用:
//错误方法
//因为构造函数是不可见的
//SingleObj object = SingleObj.getInstance();
//正确获取方法
SingleObj object = SingleObject.getInstance();
//显示消息
object.showMessage();
常用6种单例模式:
- 懒汉式,线程不安全
既然是懒汉,所以在不调用他之前,绝不创建自己对象,只有使用他的时候,才会创建对象。1234567891011public class SingleTon{private static SingleTon instance;private SingleTon(){};public static SingleTon getInstance(){if(instance == null){instance = new SingleTon();}return instance;}}
简单,方便。但是有一个致命的缺点,就是在多线程的情况之下,当多个线程同时调用getInstance的时候,很可能创建出多个实例。所以在多线程的情况之下,不能成为一个单例模式。
- 懒汉式,线程安全
既然懒汉式在多线程不安全,那么我们只需要给他加一个锁,保证单线运行就行了撒。所以我们直接给getInstance添加同步关键字(synchronized);1234567891011public class SingleTon{private static SingleTon instance;private SingleTon(){};public static synchronized SingleTon getInstance(){if(instance == null){instance = new SingleTon();}return instance;}}
保留了懒汉式的简单、方便还有懒汉模式,而且有保证的线程安全。但因为加了锁,导致效率下降,因为同一时间,只能有一个线程运行getInstance();
- 饿汉式
既然是饿汉,所以还不择食。与其担心多线程时创建多个实例,不如首先实例好,需要的时候,提供给你就好了。1234567public class SingleTon{private static SingleTon instance = new SingleTon();private SingleTon(){};public static SingleTon getInstance(){return instance;}}
简单方便,解决了多线程的问题,没有加锁,提到了运行效率。
缺点:
类加载时候就已经初始化了,内存的浪费。
如果创建该实例的时候,需要依赖其他信息或者参数的时候,就做不到。
- 双重检验锁
思路来源于懒汉式,既然给getInstance加锁导致效率下降,那么我们在创建的时候加锁呢?
即两次检验单例是否已经存在。123456789101112131415public class SingleTon{private static SingleTon instance;private SingleTon(){};public static synchronized SingleTon getInstance(){if(instance == null){synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情:
1.给 instance 分配内存
2.调用 Singleton 的构造函数来初始化成员变量
3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
复杂,且存在隐含问题。
- 静态内部类123456789public class SingleTon{private static class SingleTonHolder{private static final SingleTon instance = new SingleTon();}private SingleTon(){}public static final SingleTon getInstance(){return SingleTonHolder.instance;}}
使用了JVM本身机制保证了线程的安全,
因为静态单例对象并不是成员变量,所以在加载类的时候,不会实例化。
第一次调用getInstance的时候,加载内部类SingleTonHolder。实现实例化
既是懒汉,又有Java虚拟机保证线程安全。
- Enum枚举
神技,简单,粗暴。而且各种天生buff。123public enum EasySingleTon{INSTANCE:}
我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
总结
总结来说,一共有5中,第一种不算。懒汉,饿汉,双重检验,静态内部类,枚举。
通常情况之下,常用饿汉解决问题。
如果要求懒加载,那就用静态内部类。
如果涉及反序列化,那就使用枚举。