单例模式
概述
单例模式:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
分类
1 .最简单的形式--懒汉写法(线程不安全)
package Create.SingletonPattern;
/**
* 懒汉式
*/
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
2 . 懒汉式写法(线程安全)
package Create.SingletonPattern;
public class LazySingletonEnhence {
private static LazySingletonEnhence singleton;
private LazySingletonEnhence(){}
// 改进版 getInstance方法上加同步
public static synchronized LazySingletonEnhence getInstance() {
if (singleton == null) {
singleton = new LazySingletonEnhence();
}
return singleton;
}
}
优点:初始化才创建
缺点:每次都需要检查同步,消耗资源
3. 饿汉式写法
package Create.SingletonPattern;
/**
* 饿汉模式
*/
public class StaffSingleton {
private static final StaffSingleton single = new StaffSingleton();
private StaffSingleton() {
}
public static StaffSingleton getSingle() {
return single;
}
}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
饿汉式和懒汉式区别
- 饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
- 而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
- 另外从以下两点再区分以下这两种方式:
- 线程安全:
- 饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
- 懒汉式本身是非线程安全的。
- 资源加载和性能:
- 饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,
- 而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
- 线程安全:
4. 静态内部类
package Create.SingletonPattern;
class InternalSingleton {
private static class SingletonHolder {
private final static InternalSingleton INSTANCE = new InternalSingleton();
}
private InternalSingleton() {
}
public static InternalSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点:调用时才初始化静态变量INSTANCE,,也有效避免了DCL失效 ,所以推荐使用这种方式。
5. 枚举
package Create.SingletonPattern;
enum EnumSingleton {
INSTANCE;
public void doSomeThing(){
System.out.println("do some thing");
}
}
- 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。默认的枚举实例的创建是线程安全的,但是实例内的各种方法则需要程序员来保证线程安全
- 但是由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。
- 总的来说好处有
- 实例的创建线程安全,确保单例
2. 防止被反射创建多个实例。
3. 没有序列化的问题。
- 实例的创建线程安全,确保单例
- 对于反序列化可以通过复写钩子函数:readResolve(),返回单例对象,默认会重新生成一个新的对象。
6. 双重校验锁
package Create.SingletonPattern;
public class LockSingleton{
private volatile static LockSingleton singleton;
private LockSingleton(){}
public static LockSingleton getInstance(){
if(singleton==null){
synchronized(LockSingleton.class){
if(singleton==null){
singleton=new LockSingleton(); //非原子性操作
}
}
}
return singleton;
}
}
- singleton=new LockSingleton();是非原子性操作,分为3个步骤
- 给LockSingleton实例分配内存
- 调用构造函数;初始化成员字段
- 将singleton对象指向非配的内存空间
- 其中 2 和 3 顺序是未定的,容易引起DCL失效,需要借助volatile关键字解决这个问题
优点:初始化才实例化,效率高
缺点:第一次翻译稍慢在
7. 登记式单例
另类的单例实现,将单例类型注入到一个统一的管理类中,再根据key获取对象对应类型的对象。
package Create.SingletonPattern;
import java.util.HashMap;
import java.util.Map;
/**
* 可以忽略
*/
public class SingletonManager {
private SingletonManager() {
}
private static Map<String, Object> objectMap = new HashMap<>();
public static void registerService(String key,Object instance){
if (!objectMap.containsKey(key)){
objectMap.put(key,instance);
}
}
public static Object getService(String key){
return objectMap.get(key);
}
}