单例(Singleton)模式的定义:
指一个类只有一个实例,且该类能自行创建这个实例的一种模式
单例模式有 3 个特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点;
分为以下单例模式
1 饿汉式
/**
* 1.饿汉式
* 优点:执行效率高,性能高,没有任何的锁
* 缺点:某些情况下,可能会造成内存浪费
*/
public class HungrySingleton {
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
2 懒汉式
/**
* 2 懒汉式 + 双重检查机制
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
*/
public class LazyDoubleCheckSingleton {
// volatile关键字 防指令重排序的问题
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
//检查是否要阻塞
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
//检查是否要重新创建实例
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
测试单例模式 线程安全 测试代码
/**
* 测试单例模式 线程安全 测试代码
*/
public class TestThread implements Runnable {
@Override
public void run() {
LazySimpleSingletion instance = LazySimpleSingletion.getInstance();
System.out.println(Thread.currentThread().getName()+":"+instance);
}
public static void main(String[] args) {
Thread thread1 = new Thread(new TestThread());
Thread thread2 = new Thread(new TestThread());
thread1.start();
thread2.start();
}
}
3 内部静态类单例
/**
* 3 内部静态类单例
* 避免了内存浪费,线程安全
* 需要防止反射破坏
*/
public class LazySingleton {
private LazySingleton() {
// 防止反射破坏 (以上模式同理)
if (LazyHolder.INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static LazySingleton getInstance() {
return LazyHolder.INSTANCE;
}
// 默认不加载 调用时加载
private static class LazyHolder {
private static final LazySingleton INSTANCE = new LazySingleton();
}
}
反射破坏单例
/**
* 反射破坏单例测试类
* 解决方案 在构造器中 判断该实例是否已经创建,已创建抛出异常
*/
public class Test {
public static void main(String[] args) throws Exception{
Class<?> clz= HungrySingleton.class;
Constructor c = clz.getDeclaredConstructor();
c.setAccessible(true);
Object a = c.newInstance();
Object b = c.newInstance();
System.out.println(a);
System.out.println(b);
}
}
序列化破坏单例
/**
* 4 序列化破坏单例
* 一个单例创建对象后,有时候需要将对象序列化写入磁盘,下次使用时再从磁盘中读取对象进行反序列化,
* 将其转化为内存对象,反序列化后的对象会重新分配内存,即重新创建对象,如果序列化的对象为单例对象,就违背了单例模式
* 解决方案
* 编写readResolve方法 返回单例对象
*/
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
/**
* 编写readResolve方法 防止反序列化破坏单例
* 查看JDK源码 ObjectInputStream.readObject()中的readObject0()方法
* 从中找到readOrdinaryObject()方法中有以下代码 2053行
* [obj = desc.isInstantiable() ? desc.newInstance() : null; ]
* 在上方我可以发现已经实例化新的对象
* 接着往下看 2076行 [desc.hasReadResolveMethod()]
* 以上判断该对象是否有readResolve()方法
* 如果有调用此方法 返回readResolve里面的对象
*/
private Object readResolve(){ return INSTANCE;}
}
反序列化测试类
/**
* 反序列化测试类
*/
public class Test {
public static void main(String[] args){
SeriableSingleton s1 = SeriableSingleton.getInstance();
SeriableSingleton s2;
try {
//序列化
//把内存中对象的状态转换为字节码的形式
//把字节码通过IO输出流,写到磁盘上
//永久保存下来,持久化
FileOutputStream out = new FileOutputStream("SeriableSingleton");
ObjectOutputStream oOut = new ObjectOutputStream(out);
oOut.writeObject(s1);
oOut.flush();
oOut.close();
//反序列化
//将持久化的字节码内容,通过IO输入流读到内存中来
//转化成一个Java对象
FileInputStream inputStream = new FileInputStream("SeriableSingleton");
ObjectInputStream oIn = new ObjectInputStream(inputStream);
s2 =(SeriableSingleton) oIn.readObject();
oIn.close();
System.out.println(s1);
System.out.println(s2);
} catch (Exception e) {
e.printStackTrace();
}
// todo 关闭流
}
}
5 枚举式单例
/**
* 4 枚举式单例
* 1是饿汉式单例模式的实现 反编译该class 可看到 静态代码块给 INSTANCE 赋值
* 2不会被反序列化破坏
* 同理 查看JDK源码 ObjectInputStream.readObject()中的readObject0()方法
* 从中找到 readEnum()方法 中的代码2011行
* [Enum<?> en = Enum.valueOf((Class)cl, name);]
* 可以看出 枚举类型通过类名和类对象找到一个唯一的枚举对象,因此枚举对象不可能被类加载器加载多次.
* 3不会被反射破坏
* 在 JDK源码中 Constructor.newInstance方法中
* 不允许反射创建枚举实例
*/
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){return INSTANCE;}
}
6 容器式单例
/**
* 容器式单例
* 适用于大量创建单例对象
* 线程不安全
*/
public class ContainerSingleton {
private ContainerSingleton() {}
private static final Map<String, Object> IOC = new ConcurrentHashMap<>();
public static Object getInstance(String className) {
Object instance = null;
synchronized (IOC) {
if (!IOC.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
IOC.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
} else {
return IOC.get(className);
}
}
}
}
7 ThreadLocal单例
/**
* ThreadLocal单例
* 保证线程内部的全局唯一,当前线程唯一
* 其他单例模式,为了线程安全,给方法加锁,以时间换空间
* ThreadLocal将所有对象全部放在ThreadLocalMap中,为每个线程提供一个对象,实际上是以空间换时间来实现线程隔离的
*/
public class ThreadLocalMapSingleton {
private static final ThreadLocal<Map<String,Object>> THREAD_LOCAL_INSTANCE;
static {
THREAD_LOCAL_INSTANCE = new ThreadLocal<Map<String, Object>>() {
@Override
protected Map<String, Object> initialValue() {
return new HashMap<>(10);
}
};
}
private ThreadLocalMapSingleton() {}
public static Map<String, Object> getInstance() {
return THREAD_LOCAL_INSTANCE.get();
}
}
总结
饿汉式(静态块的饿汉式)
内存浪费
懒汉式
简单懒汉式 性能问题
双重检查锁 不优雅
静态内部类 可被发射破坏
注册式
枚举式 原理就是静态块的饿汉式
容器式 spring采用