单例模式
概念
单例模式(SingletonPattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式属于创建型模式。
饿汉式单例模式
典型饿汉式单例模式
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
饿汉式静态块单例模式
public class HungryStaticSingleton {
private static final HungryStaticSingleton instance;
static {
instance = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return instance;
}
}
优点
没有加任何锁、执行效率比较高
缺点
类加载的时候就初始化,不管用与不用都占着空间,浪费内存
懒汉式单例模式
简单懒汉式单例实现
public class LazySimpleSingleton {
private static LazySimpleSingleton instance;
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if (instance == null){
instance = new LazySimpleSingleton();
}
return instance;
}
}
在多线程环境下,这种写法存在安全隐患,可能创建出两个不同的实例,违背了单例原则。优化如下:
/**
* 优点:节省了内存,线程安全
* 缺点:性能低
*/
public class LazySimpleSingleton {
private static LazySimpleSingleton instance;
private LazySimpleSingleton(){}
public synchronized static LazySimpleSingleton getInstance(){
if (instance == null){
instance = new LazySimpleSingleton();
}
return instance;
}
}
通过 synchronized 关键字加锁可以解决线程安全问题,但会降低性能。
双重检查锁单例(DCL单例)
/**
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
*/
public class DCLSingleton {
private volatile static DCLSingleton instance;
private DCLSingleton(){}
public static DCLSingleton getInstance(){
if (instance == null){
synchronized (DCLSingleton.class){
if (instance == null){
instance = new DCLSingleton();
}
}
}
return instance;
}
}
静态内部类单例
//这种形式兼顾饿汉式单例模式的内存浪费问题和 synchronized 的性能问题
//完美地屏蔽了这两个缺点
public class InnerClassSingleton {
private InnerClassSingleton(){}
public static final InnerClassSingleton getInstance(){
return LazyHolder.INNER_CLASS_SINGLETON;
}
private static class LazyHolder{
private static final InnerClassSingleton INNER_CLASS_SINGLETON = new InnerClassSingleton();
}
}
反射破坏单例
以 InnerClassSingleton 为例:
public class InnerClassSingletonTest {
public static void main(String[] args) {
try {
Class clazz = InnerClassSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
InnerClassSingleton instance = (InnerClassSingleton) constructor.newInstance();
InnerClassSingleton instance2 = (InnerClassSingleton) constructor.newInstance();
System.out.println(instance == instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果为 false ,单例被破坏。
对 InnerClassSingleton 进行优化,防止反射破坏单例。
public class InnerClassSingleton {
private InnerClassSingleton(){
if (LazyHolder.INNER_CLASS_SINGLETON != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static final InnerClassSingleton getInstance(){
return LazyHolder.INNER_CLASS_SINGLETON;
}
private static class LazyHolder{
private static final InnerClassSingleton INNER_CLASS_SINGLETON = new InnerClassSingleton();
}
}
注册式单例之枚举式单例
枚举式单例典型写法如下:
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 static void main(String[] args) {
// EnumSingleton instance = EnumSingleton.getInstance();
try {
Class clazz = EnumSingleton.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
//System.out.println(constructor);
constructor.setAccessible(true);
Object instance = constructor.newInstance();
System.out.println(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.aaron.pattern.singleton.test.EnumSingletonTest.main(EnumSingletonTest.java:15)
可以知道,枚举类型无法通过反射创建实例。
注册式单例之容器式单例
public class ContainerSingleton {
private ContainerSingleton() {}
private static Map ioc = new ConcurrentHashMap<>();
public static Object getInstance(String className){
Object obj = null;
synchronized (ioc) {
if (!ioc.containsKey(className)) {
try {
obj = Class.forName(className).newInstance();
ioc.putIfAbsent(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
序列化破坏单例
一个单例对象创建好后,有时候需要将对象序列化然后写入磁盘,下次使用时再从磁盘中读取对象
并进行反序列化,将其转化为内存对象。反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,来看一段代码:
//反序列化导致破坏单例模式
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;
}
}
测试代码如下
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
测试代码运行结果如下:
com.aaron.pattern.singleton.seriable.SeriableSingleton@6d03e736
com.aaron.pattern.singleton.seriable.SeriableSingleton@14ae5a5
false
可以通过增加 readResolve() 方法保证单例,代码优化:
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式
//从而转换一个 I/O 流,写入其他地方(可以是磁盘、网络 I/O)
//内存中的状态会永久保存下来
//反序列化就是将已经持久化的字节码内容转换为 I/O 流
//通过 I/O 流的读取,进而将读取的内容转换为 Java 对象
//在转换过程中会重新创建对象 new
private static final SeriableSingleton instance = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return instance;
}
private Object readResolve() {
return instance;
}
}
再次运行测试:
com.aaron.pattern.singleton.seriable.SeriableSingleton@14ae5a5
com.aaron.pattern.singleton.seriable.SeriableSingleton@14ae5a5
true