设计模式之单例模式

单例模式

概述

单例模式:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。

分类

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特性,用这种方式写不免让人感觉生疏。
  • 总的来说好处有
    1. 实例的创建线程安全,确保单例
      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个步骤
    1. 给LockSingleton实例分配内存
    2. 调用构造函数;初始化成员字段
    3. 将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);
    }

}

see source code

原文地址:https://www.cnblogs.com/Dyleaf/p/8507015.html