单例模式(创建)
定义与类型
定义:保证一个类仅有一个实例,并提供一个全局访问点
类型:创建型
UML
单例模式的基本要素
私有的构造方法
指向自己实例的私有静态引用
以自己实例为返回值的静态的公有的方法
适用场景
像确保任何情况下都绝对只有一个实例
需要频繁实例化然后销毁的对象。
创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
有状态的工具类对象。
频繁访问数据库或文件的对象。
优点
在内存里只有一个实例,减少了内存开销
可以避免对资源的多重占用
避免重复创建对象,提高性能
设置全局访问点,严格控制访问
缺点
没有接口,扩展困难
违反开闭原则
单例模式的几种实现方式
1.饿汉式
饿汉式:顾名思义,对象比较饥饿,所以一开始就创建好了。饿汉式也是单例模式的最简单实现。
/** 饿汉式 一开始就new好了 */
public class HungrySingleton implements Serializable {
/** 可以直接new也可以适用静态块中创建 */
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton1();
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
/**
* 私有构造函数
*/
private HungrySingleton() {}
}
饿汉式的单例模式,对象一开始就创建好了。不需要考虑线程安全问题。
饿汉式单例模式如果消耗资源比较多,而对象未被适用则会造成资源浪费。
2.懒汉式
懒汉式:说明类对象比较懒,没有直接创建,而是延迟加载的,是第一次获取对象的时候才创建。懒汉式的单例模式应用较多。
A、第一个版本的Java实现(非线程安全)
/** 懒汉式 线程不安全 */
public class LazySingleton {
private static LazySingleton lazySingleton = null;
//线程不安全,当有两个线程同时创建对象,会违背单例模式
public static LazySingleton getInstance() {
if (lazySingleton == null) {
//会发生指令重排
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
private LazySingleton() {}
}
这个版本的懒汉式会出现线程安全的问题,当两个线程同时访问getInstance()静态方法时,lazySingleton还未创建,就会创建出两个实例,违背了单例模式。
这里可以在getInstance()方法添加同步锁synchronized解决,也可以在方法体添加类锁,但是这样相当于完全锁住了getInstance(),会出现性能问题。
推荐适用下面这种方式
B、双重检查锁double check懒汉式(线程安全,通常适用这种方式)
/** 懒汉式 线程安全 */
public class LazyDoubleCheckSingleton {
//volatile 禁止指令重排序
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
/** 在静态方法中直接加synchronized相当于锁了类 */
public static LazyDoubleCheckSingleton getInstance() {
//同样实锁类, 指令重排序
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
/**
* 1.分配内存给这个对象
* 2.初始化对象
* 3.设置lazyDoubleCheckSingleton指向刚分配的内存
* 2 3 顺序有可能发生颠倒
* intra-thread semantics 不会改变单线程执行结果,指令重排序
*/
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
private LazyDoubleCheckSingleton() {}
}
双重检查,只有对象为空的时候才会需要同步锁,而第二次判断是否为null,是对象是否已经创建。
添加volatile关键字,防止指令重排序。
C、基于静态内部类的延迟加载方案
私有静态类的延迟加载
将延迟初始化交给静态类的初始化
public class StaticInnerClassSingleton {
/**
* 看静态类的初始化锁那个线程可以拿到
*/
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton () {
if (InnerClass.staticInnerClassSingleton != null) {
throw new RuntimeException("单例对象禁止反射调用");
}
}
}
3.容器单例
使用静态容器方式来实现多单例类
public class ContainerSingleton {
//静态容器, 注意map不是线程安全的,如果为了线程安全可以使用HashTable或者ConcurrentHashMap
private static Map<String, Object> singletonMap = new HashMap<>();
public static void putInstance (String key, Object instance) {
if (key != null && key.length() != 0) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
public static Object getInstance (String key) {
return singletonMap.get(key);
}
}
容器单例如果要保证线程安全性,建议使用ConcurrentHashMap
通常使用容器单例情况是:单例对象比较多,需要统一维护。
4、枚举单例模式(推荐使用)
枚举单例是从JVM层面上做的限制
public enum EnumInstance {
/**
* 具体的单例实例
*/
INSTANCE {
protected void printTest () {
System.out.println("K.O print Test!");
}
};
private Object data;
protected abstract void printTest();
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance() {
return INSTANCE;
}
}
单例模式完美防御了反射与序列化攻击
5、ThreadLocal线程单例(并不是严格意义上的单例模式)
有一部分场景,要求对象的生命周期随着线程
/**
* 线程级单例模式
*/
public class ThreadLocalInstance {
//静态的ThreadLocal类保存对象
private static final ThreadLocal<ThreadLocalInstance> threadLocal =
ThreadLocal.withInitial(ThreadLocalInstance::new);
private ThreadLocalInstance () {}
public static ThreadLocalInstance getInstance () {
return threadLocal.get();
}
}
单例模式关注的重点
1、私有构造器
2、线程安全
3、延迟加载
4、序列化和反序列化安全
5、反射攻击安全
相关设计模式
单例模式和工厂模式:工厂类可以设计成单例模式。
单例模式和享元模式:可以通过享元模式来获取单例对象
- 感谢你赐予我前进的力量