单例模式:
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
饿汉式单例:
饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。
public class HungrySingleton {
//类加载时初始化
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
//全局访问点
public HungrySingleton getInstance(){
return instance;
}
}
优点:没有加任何锁、执行效率比较高,用户体验比懒汉式单例模式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能“占着茅坑不拉屎”。
懒汉式单例:
懒汉式单例模式是被外部类调用的时候内部类才会加载,它是线程不安全的。
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
//外部类调用的时候内部加载
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
很明显这样子的懒汉式单例是线程不安全的,如何保证其线程安全呢?于是呢,我们给获取实例的方法加上锁
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
synchronized可保证线程安全,但是,用 synchronized 加锁时,在线程数量比较多的情况下,如果CPU分配压力上升,则会导致大批线程阻塞,从而导致程序性能大幅下降。于是我们改进加锁方法:
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class){
if (instance == null)
instance = new LazySingleton();
}
}
return instance;
}
}
使用双重检查锁,在保证线程安全的情况下,相比于直接加锁提升了性能。但是,使用synchronized总归要降低性能,我们从类初始化的角度下考虑,使用静态内部类。
public class LazySingleton {
private LazySingleton() {}
public static final LazySingleton getInstance() {
return LazyInnerHolder.LAZY;
}
//初始化LazySingleton要先初始化LazyInnerHolder,
private static class LazyInnerHolder{
private static LazySingleton LAZY = new LazySingleton();
}
}
这种方式兼顾了饿汉式单例模式的内存浪费问题和 synchronized 的性能问题。内部类一定是要在方法调用之前初始化,巧妙地避免了线程安全问题。
注册式单例
注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识 获取实例。注册式单例模式有两种:一种为枚举式单例模式,另一种为容器式单例模式。
注册式单例可解决反射和序列化导致的破坏单例行为,先埋坑以后再填。
主要是在反射机制和序列化readResolve中解决
枚举式单例
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;
}
}
容器式单例
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getBean(String className){
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj); } catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
容器式单例模式适用于实例非常多的情况,便于管理。但它是非线程安全的。 Spring 中的容器式单例模式的实现代码:
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
/** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */
private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16); ...
}
破坏单例模式
反射机制
其实以上的例子已经很好的说明了单例模式的用法,但是呢,通过Java 的反射机制,我们可以轻易的去调用其构造方法,然后再去调用getInstance(),就会创建出来不同的实例。
public static void main(String[] args) {
Class clazz = LazySingleton.class;
try {
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Object o = constructor.newInstance();
Object o2= constructor.newInstance();
System.out.println(o==o2);
} catch (Exception e) {
e.printStackTrace();
}
}
--------------------------------------------------------------------------------------------
false
Process finished with exit code 0
序列化
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象 并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化 的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例。
//反序列化导致破坏单例模式
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式 //从而转换一个 I/O 流,写入其他地方(可以是磁盘、网络 I/O)
//内存中的状态会永久保存下来 //反序列化就是将已经持久化的字节码内容转换为 I/O 流
//通过 I/O 流的读取,进而将读取的内容转换为 Java 对象 //在转换过程中会重新创建对象 new
public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
}
反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单 例模式的设计初衷。那么,我们如何保证在序列化的情况下也能够实现单例模式呢?其实很简单,只需 要增加 readResolve()方法即可。(这里需要JDK的ObjectInputStream类的readObject()方法).
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式 //从而转换一个 I/O 流,写入其他地方(可以是磁盘、网络 I/O)
//内存中的状态会永久保存下来 //反序列化就是将已经持久化的字节码内容转换为 I/O 流
//通过 I/O 流的读取,进而将读取的内容转换为 Java 对象 //在转换过程中会重新创建对象 new
public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}