参考链接
2 单例模式
3 枚举单例(如果枚举类实例参数有多个,那么枚举单例将失效)
概念及作用
单例模式是指在内存中只会创建一次对象的设计模式,让所有需要调用的地方共享改对象。避免了程序中多次使用同一对象时频繁创建对象导致内存升高。
类型
懒汉式
说明
当程序使用对象时,判断对象是否实例化,未实例化则实例化改对象,已实例化则直接返回。
懒汉式单例需要私有构造方法,双重校验非空并加锁。
/**
* 懒汉式单例模式
* @author linmeng
* @version 1.0
* @date 2021年11月9日 21:25
*/
public class LazySingleton {
private static volatile LazySingleton lazySingleton;
private LazySingleton(){}
/**
*
* 懒汉式单例:双重校验并加锁,但是new 对象不是原子性操作,还是有可能并发安全问题
*
* @author linmeng
* @date 2021年11月9日 21:27
* @return com.sword.www.designPattern.Singleton
*/
public static LazySingleton getLazyInstance(){
if (lazySingleton ==null){
synchronized (LazySingleton.class){
if (lazySingleton ==null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
流程
当多个线程进入时,先判空,这个时候有可能有多个线程进行抢锁,但是只有一个线程抢到锁,另一线程只能等待该线程创建对象,该线程创建完对象直接返回,这时候另一个线程加锁,里面再来一个非空判断,直接让他拿到对象。
volatile关键字作用
jvm创建对象分为三个步骤
- 为对象分配空间
- 初始化对象
- 将初始化的对象指向分配的空间
jvm在执行语句时,为了提高性能,有可能不按照代码顺序执行。这时候,代码执行顺序有可能变成1 3 2.这个时候一个线程走到3那一步,但是没有初始化对象,另一个线程判断非空,对象不为空,直接返回未初始化的对象进行使用,这就会造成空指针。
但是用了volatile关键字,禁止指令重排,就不会出现这种情况。
饿汉式
说明
在程序加载时创建对象,调用时直接返回
/**
* 饿汉式单例模式
* @author linmeng
* @version 1.0
* @date 2021年11月9日 21:25
*/
public class EagerSingleton {
private static final EagerSingleton eagerSingleton = new EagerSingleton();
private EagerSingleton(){}
/**
*
* 直接返回
* @author linmeng
* @date 2021年11月9日 21:44
* @return com.sword.www.designPattern.singleton.EagerSingleton
*/
public static EagerSingleton getEagerInstance(){
return eagerSingleton;
}
}
单例模式破坏
反射破坏
反射可以使用私有构造器破坏单例模式
/**
*
* 反射破坏单例模式
* @param args
* @author linmeng
* @date 2021年11月9日 21:45
* @return void
*/
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 获取构造器
Constructor<EagerSingleton> constructor = EagerSingleton.class.getDeclaredConstructor();
// 设置可访问私有构造
constructor.setAccessible(true);
// 反射创建对象
EagerSingleton eagerSingleton = constructor.newInstance();
// 单例创建对象
EagerSingleton lazyInstance = EagerSingleton.getLazyInstance();
// 判断对象是否同一个
System.out.println(lazyInstance==eagerSingleton);
}
序列化反序列化破坏
序列化反序列化可以破坏单例模式,对象序列化成一个文件,然后反序列化时读取这个文件会生成一个新的对象,导致生成两个对象
public static void main(String[] args) throws Exception {
// 创建文件输出流
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("LazySingleton.file"));
// 写入文件
outputStream.writeObject(LazySingleton.getLazyInstance());
// 文件中读取单例对象
File file = new File("LazySingleton.file");
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
LazySingleton lazySingleton = (LazySingleton) inputStream.readObject();
System.out.println(lazySingleton==LazySingleton.getLazyInstance());
}
静态内部类单例
类中添加一个静态内部类,静态内部类使用饿汉式创建对象
public class StaticNestSingleton {
private StaticNestSingleton(){}
private static class SingletonHolder{
private static final StaticNestSingleton SINGLETON = new StaticNestSingleton();
}
/**
*
* 直接返回
* @author linmeng
* @date 2021年11月9日 21:44
* @return com.sword.www.designPattern.singleton.EagerSingleton
*/
public static StaticNestSingleton getStaticNestInstance(){
return SingletonHolder.SINGLETON;
}
public static void main(String[] args) throws Exception{
Constructor<StaticNestSingleton> constructor = StaticNestSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
StaticNestSingleton staticNestSingleton = constructor.newInstance();
System.out.println(staticNestSingleton==StaticNestSingleton.getStaticNestInstance());
// 创建文件输出流
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("StaticNestSingleton.file"));
// 写入文件
outputStream.writeObject(StaticNestSingleton.getStaticNestInstance());
// 文件中读取单例对象
File file = new File("StaticNestSingleton.file");
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
StaticNestSingleton staticNestSingleton2 = (StaticNestSingleton) inputStream.readObject();
System.out.println(staticNestSingleton2==StaticNestSingleton.getStaticNestInstance());
}
}
相当于懒汉跟饿汉优点集成,类初始化是不会初始化内部类,只有用到的时候才会初始化。为什么线程安全见参考链接。缺点是反射和序列化都能破坏单例。
终极大招(枚举式)
public enum EnumSingleton {
INSTANCE;
EnumSingleton() { System.out.println("枚举创建对象了"); }
public static void main(String[] args) { /* test(); */ }
public static void main() {
EnumSingleton t1 = EnumSingleton.INSTANCE;
EnumSingleton t2 = EnumSingleton.INSTANCE;
System.out.println(t1 == t2);
}
}
优劣势见参考链接2
如果枚举类中有多个枚举,多线程会失效。见参考链接3.这个是人为bug,生死有命富贵在天。文章有些草率,后续再优化。