单例模式,你真的理解了吗?
Posted 极智编码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式,你真的理解了吗?相关的知识,希望对你有一定的参考价值。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点,
主要解决:一个全局使用的类频繁地创建与销毁。
创建方式:两种创建方式,一种是饿汉式创建,另外一种是懒汉式创建
1.饿汉式创建 :在类加载时就已创建好对象,而不是在需要时在创建对象
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
/**
* 私有构造函数,不能被外部所访问
*/
private HungrySingleton() {}
/**
* 返回单例对象
* */
public static HungrySingleton getHungrySingleton() {
return hungrySingleton;
}
}
构造函数私有化,保证外部不能调用构造函数创建对象,创建对象的行为只能由这个类决定
只能通过
getHungrySingleton
方法获取对象HungrySingleton
对象已经创建完成【在类加载时创建】
如果getHungrySingleton
一直没有被使用到,有点浪费资源,可以保证线程安全
2.懒汉式创建:在第一次需要该对象时再创建
(1)线程不安全
public class LazySingleton {
private static LazySingleton lazySingleton = null;
/**
* 构造函数私有化
* */
private LazySingleton() {
}
private static LazySingleton getLazySingleton() {
if (lazySingleton == null) {
return new LazySingleton();
}
return lazySingleton;
}
}
当需要时【getLazySingleton方法调用时】才创建 嗯,好像没什么问题,但是当有多个线程同时调用getLazySingleton方法时,此时刚好对象没有初始化,两个线程同时通过lazySingleton == null的校验,将会创建两个LazySingleton对象。必须搞点手段使getLazySingleton方法是线程安全的
(2)使用synchronized保证线程安全
public class LazySynchronizeSingleton {
private static LazySynchronizeSingleton lazySynchronizeSingleton= null;
/**
* 构造函数私有化
* */
private LazySynchronizeSingleton() {
}
public synchronized static LazySynchronizeSingleton getLazySynchronizeSingleton() {
if (lazySynchronizeSingleton == null) {
lazySynchronizeSingleton = new LazySynchronizeSingleton();
}
return lazySynchronizeSingleton;
}
}
(3)使用lock保证线程安全
public class LazyLockSingleton {
private static LazyLockSingleton lazyLockSingleton = null;
/**
* 锁
**/
private static Lock lock = new ReentrantLock();
/**
* 构造函数私有化
* */
private LazyLockSingleton() {
}
public static LazyLockSingleton getLazyLockSingleton() {
try {
lock.lock();
if (lazyLockSingleton == null) {
lazyLockSingleton = new LazyLockSingleton();
}
} finally {
lock.unlock();
}
return lazyLockSingleton;
}
}
以上两种方法给方法加锁无论对象是否已经初始化都会造成线程阻塞。如果对象为null
的情况下才进行加锁,对象不为null
的时候则不进行加锁,那么性能将会得到提升,双重锁检查则可以实现这个需求。
3.双重检查锁创建:在加锁之前先判断lazyDoubleCheckSingleton == null
是否成立,如果不成立就直接返回创建好的对象,成立再进行加锁
public class LazyDoubleCheckSingleton {
/**
* 使用volatile进行修饰,禁止指令重排
* */
private static volatile LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
/**
* 构造函数私有化
* */
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getLazyDoubleCheckSingleton() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
为什么需要对lazyDoubleCheckSingleton添加volatile修饰符, 因为lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();不是原子性的,分为三步:
一.为lazyDoubleCheckSingleton分配内存
二. 调用构造函数进行初始化
三.将lazyDoubleCheckSingleton对象指向分配的内存【执行完这步lazyDoubleCheckSingleton将不为null】
为了提高程序的运行效率,编译器会进行一个指令重排,步骤2和步骤三进行了重排,线程1先执行了步骤一和步骤三,执行完后,lazyDoubleCheckSingleton不为null,此时线程2执行到if (lazyDoubleCheckSingleton == null),线程2将可能直接返回未正确进行初始化的lazyDoubleCheckSingleton对象。出错的原因主要是lazyDoubleCheckSingleton未正确初始化完成【写】,但是其他线程已经读取lazyDoubleCheckSingleton的值【读】,使用volatile可以禁止指令重排序,通过内存屏障保证写操作之前不会调用读操作【执行if (lazyDoubleCheckSingleton == null)】
4.静态内部类式创建
public class LazyStaticSingleton {
/**
* 静态内部类
* */
private static class LazyStaticSingletonHolder {
private static LazyStaticSingleton lazyStaticSingleton = new LazyStaticSingleton();
}
/**
* 构造函数私有化
* */
private LazyStaticSingleton() {
}
public static LazyStaticSingleton getLazyStaticSingleton() {
return LazyStaticSingletonHolder.lazyStaticSingleton;
}
}
静态内部类在调用时才会进行初始化,因此是懒汉式的,LazyStaticSingleton lazyStaticSingleton = new LazyStaticSingleton();看似是饿汉式的,但是只有调用getLazyStaticSingleton时才会进行初始化,线程安全由ClassLoad保证,不用考虑加锁。
以上几种方式实现单例的方式虽然各有优缺点,也基本实现了单例线程安全的要求。但可能会有人会对它进行攻击。对它进行攻击无非就是创建不只一个类,java
中创建对象的方式有new
、clone
、序列化、反射。构造函数私有化不可能通过new创建对象、同时单例类没有实现Cloneable
接口无法通过clone
方法创建对象,那剩下的攻击只有反射攻击和序列化攻击了
1.反射攻击
public class ReflectAttackTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//静态内部类
LazyStaticSingleton lazyStaticSingleton = LazyStaticSingleton.getLazyStaticSingleton();
//通过反射创建LazyStaticSingleton
Constructor<LazyStaticSingleton> constructor = LazyStaticSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazyStaticSingleton lazyStaticSingleton1 = constructor.newInstance();
//打印结果为false,说明又创建了一个新对象
System.out.println(lazyStaticSingleton == lazyStaticSingleton1);
//synchronize
LazySynchronizeSingleton lazySynchronizeSingleton = LazySynchronizeSingleton.getLazySynchronizeSingleton();
Constructor<LazySynchronizeSingleton> lazySynchronizeSingletonConstructor = LazySynchronizeSingleton.class.getDeclaredConstructor();
lazySynchronizeSingletonConstructor.setAccessible(true);
LazySynchronizeSingleton lazySynchronizeSingleton1 = lazySynchronizeSingletonConstructor.newInstance();
System.out.println(lazySynchronizeSingleton == lazySynchronizeSingleton1);
//lock
LazyLockSingleton lazyLockSingleton = LazyLockSingleton.getLazyLockSingleton();
Constructor<LazyLockSingleton> lazyLockSingletonConstructor = LazyLockSingleton.class.getDeclaredConstructor();
lazyLockSingletonConstructor.setAccessible(true);
LazyLockSingleton lazyLockSingleton1 = lazyLockSingletonConstructor.newInstance();
System.out.println(lazyLockSingleton == lazyLockSingleton1);
//双重锁检查
LazyDoubleCheckSingleton lazyDoubleCheckSingleton = LazyDoubleCheckSingleton.getLazyDoubleCheckSingleton();
Constructor<LazyDoubleCheckSingleton> lazyDoubleCheckSingletonConstructor = LazyDoubleCheckSingleton.class.getDeclaredConstructor();
lazyDoubleCheckSingletonConstructor.setAccessible(true);
LazyDoubleCheckSingleton lazyDoubleCheckSingleton1 = lazyDoubleCheckSingletonConstructor.newInstance();
System.out.println(lazyDoubleCheckSingleton == lazyDoubleCheckSingleton1);
}
}
以上测试打印结果都为false
2.反序列化攻击
public class SerializableAttackTest {
public static void main(String[] args) {
//懒汉式
HungrySingleton hungrySingleton = HungrySingleton.getHungrySingleton();
//序列化
byte[] serialize = SerializationUtils.serialize(hungrySingleton);
//反序列化
HungrySingleton hungrySingleton1 = SerializationUtils.deserialize(serialize);
System.out.println(hungrySingleton == hungrySingleton1);
//双重锁
LazyDoubleCheckSingleton lazyDoubleCheckSingleton = LazyDoubleCheckSingleton.getLazyDoubleCheckSingleton();
byte[] serialize1 = SerializationUtils.serialize(lazyDoubleCheckSingleton);
LazyDoubleCheckSingleton lazyDoubleCheckSingleton11 = SerializationUtils.deserialize(serialize1);
System.out.println(lazyDoubleCheckSingleton == lazyDoubleCheckSingleton11);
//lock
LazyLockSingleton lazyLockSingleton = LazyLockSingleton.getLazyLockSingleton();
byte[] serialize2 = SerializationUtils.serialize(lazyLockSingleton);
LazyLockSingleton lazyLockSingleton1 = SerializationUtils.deserialize(serialize2);
System.out.println(lazyLockSingleton == lazyLockSingleton1);
//synchronie
LazySynchronizeSingleton lazySynchronizeSingleton = LazySynchronizeSingleton.getLazySynchronizeSingleton();
byte[] serialize3 = SerializationUtils.serialize(lazySynchronizeSingleton);
LazySynchronizeSingleton lazySynchronizeSingleton1 = SerializationUtils.deserialize(serialize3);
System.out.println(lazySynchronizeSingleton == lazySynchronizeSingleton1);
//静态内部类
LazyStaticSingleton lazyStaticSingleton = LazyStaticSingleton.getLazyStaticSingleton();
byte[] serialize4 = SerializationUtils.serialize(lazySynchronizeSingleton);
LazyStaticSingleton lazyStaticSingleton1 = SerializationUtils.deserialize(serialize4);
System.out.println(lazyStaticSingleton == lazyStaticSingleton1);
}
}
测试打印结果都为false
,都存在反序列化攻击问题
下面介绍一种枚举的方式创建单例,可以避免上面存在的攻击
枚举方式创建单例:
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getEnumSingleton() {
return INSTANCE;
}
}
一共8行代码,相当完美。
1.枚举类序列化攻击
public class EnumAttackTest {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
EnumSingleton enumSingleton = EnumSingleton.getEnumSingleton();
//序列化攻击
byte[] serialize = SerializationUtils.serialize(enumSingleton);
Object enumSingleton1 = SerializationUtils.deserialize(serialize);
System.out.println(enumSingleton == enumSingleton1);
}
}
测试打印结果为true,可以防止序列化攻击
2.枚举类反射攻击
public class EnumAttackTest {
public static void main(String[] args) throws IllegalAccessException, InvocationTargetException,InstantiationException,NoSuchMethodException {
//反射攻击
Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor();
enumSingletonConstructor.setAccessible(true);
EnumSingleton enumSingleton2 = enumSingletonConstructor.newInstance();
System.out.println(enumSingleton == enumSingleton2);
}
}
运行上述代码会出现以下问题:
Exception in thread "main" java.lang.NoSuchMethodException: EnumSingleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at EnumAttackTest.main(EnumAttackTest.java:17)
这是因为所有Java枚举都隐式继承自Enum抽象类,而Enum抽象类根本没有无参构造方法,只有如下一个构造方法:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
那么我们就改成获取这个有参构造方法,即:
Constructor constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
结果还是会抛出异常:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at EnumAttackTest.main(EnumAttackTest.java:19)
来到Constructor.newInstance()方法中,有如下语句:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
可见,JDK反射机制内部完全禁止了用反射创建枚举实例的可能性。因此,枚举方法可以防止反射攻击。
以上是关于单例模式,你真的理解了吗?的主要内容,如果未能解决你的问题,请参考以下文章