
一.前言
在日常开发中或者在面试中用到的设计模式最多的就是单例模式,这篇文章简单的讲一下单例,包括单例的概念,好处,特点,几种写法及怎样防止攻击单例。
二.概念
单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例
三.好处
对于频繁使用的对象,可以省略创建对象的时间,由于new操作的次数减少,因而对系统内存的使用频率也会降低,将减轻GC压力,缩短GC停顿时间。
四.特点
(1)单例类只有一个实例
(2)单例类必须只能自己创建自己的唯一实例
(3)单例类必须给所有其他对象提供这一实例
五.几种写法和特点
饿汉式
public class Singleton{
private Singleton(){}
private static Singleton single =new Singleton();
public static Singleton getInstance(){
return single;
}
}
特点:在类加载初始化时就创建好一个静态对象供外部使用,线程安全,缺点是不能延迟加载
改善:懒汉式
懒汉式
public class Singleton{
private Singleton(){}
private static Singleton single =null;
public static Singleton getInstance(){
if(single = null){
single = new Singleton();
}
return single;
}
}
特点:虽然采用延迟加载方式实现,但在多线程并发下无法保证实例唯一。
改善:懒汉线程安全
使用synchronized同步锁
public class Single{
private Singleton(){}
private static Singleton =null;
public static Singleton getInstance(){
synchronized(Singleton.class){
if(single ==null){
single = new Singleton();
}
}
return single;
}
}
特点:虽然解决了多实例问题,但是运行效率低下,下一个线程想要获取对象,须等待上一个线程释放锁之后,才可以运行。解决DCL
双重检查锁
public class Singleton{
private Singleton(){}
private static Singleton single =null;
public static Singleton getInstance(){
if(single == null){
synchronized (Singleton.class){
if(single ==null){
single = new Singleton();
}
}
}
return single;
}
}
特点:避免整个方法都被锁,只对需要锁的代码部分加锁,可以提高执行效率。多线程的情况下可能发生JVM即时编译指令重排序 解决:volatile 优化:静态内部类,枚举
指令流水线是并行工作,多个指令可以同时处于同一个阶段,只要CPU内部相应的处理部件未被占满即可。这样乱序就可能产生了,比如两条访存指令,可能由于第二条指令命中了cache而导致它先于第一条指令完成。
静态内部类
public class Singleton{
private static class SingletonHolder{
private static final Singleton instance =new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.instance;
}
}
利用了静态变量的唯一性
优点:延迟加载 JVM本身机制保证了线程安全,没有性能缺陷,因为final static
枚举
public enum EnumSingle{
INSTANCE;
public void doSomething(){}
}
优点:写法简单 线程安全 但占内存较高
六.怎样通过反射攻击单例
首先通过反射获取构造函数,然后调用setAccessible(true) 就可以调用私有的构造函数,如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
具体做法:
可以设置一个标志位默认是false,当创建第一个实例的时候将标志位设置为true,创建第二个实例的时候抛出异常。
上面实现单例的几种方法中除了枚举类型外,其他方式都会被反射攻击,即使它的构造方法是私有的。
//以上面的静态内部类为例
//SingletonAttack为模拟攻击的类
public class SingletonAttack {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Class<?> classType = Singleton01.class;
Constructor<?> constructor =classType.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton01 singleton01 = (Singleton01)constructor.newInstance();
Singleton01 singleton02 = Singleton01.getInstance();
System.out.print(singleton01==singleton02);
}
}
运行结果为false,即singleton01和single02是两个不同的实例。
解决:
SingletonAttack修改
private static boolean flag = false;//定义一个标志位,用于判断是否是第一次创建实例
private static class SingletonHolder{
private static final Singleton01 instance =new Singleton01();
}
private Singleton01(){
synchronized (Singleton01.class){
if(!flag){
flag = !flag;//创建实例后就设置为true
}else {//第二次创建实例就抛出运行时异常
throw new RuntimeException("singleton attacked");
}
}
}
public static final Singleton01 getInstance(){
return SingletonHolder.instance;
}
Exception in thread "main" java.lang.ExceptionInInitializerError
at table.fsp.com.javatest.Singleton01.getInstance(Singleton01.java:22)
at table.fsp.com.javatest.SingletonReflectAttack.main(SingletonAttack.java:18)
Caused by: java.lang.RuntimeException: singleton attacked
at table.fsp.com.javatest.Singleton01.<init>(Singleton01.java:17)
at table.fsp.com.javatest.Singleton01.<init>(Singleton01.java:7)
at table.fsp.com.javatest.Singleton01$SingletonHolder.<clinit>(Singleton01.java:10)
... 2 more
可以看到抛出运行时异常了,成功阻止攻击单例。
另外枚举类型也是可以阻止反射攻击的
public enum Singleton01 {
INSTANCE;
public void doSomething(){}
}
public class SingletonAttack{
public static void main(String[] args) throws InstantiationException,IllegalAccessException, IllegalArgumentException, InvocationTargetException,NoSuchMethodException, SecurityException {
Class<?> classType = Singleton01.class;
Constructor<?> constructor =classType.getDeclaredConstructor();
constructor.setAccessible(true);
constructor.newInstance();
}
}
运行结果
Exception in thread "main" java.lang.NoSuchMethodException: table.fsp.com.javatest.Singleton01.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at table.fsp.com.javatest.SingletonReflectAttack.main(SingletonAttack.java:15)
从结果看也是抛出了异常,阻止了反射攻击。
七.总结
本文介绍了单例的概念,几种写法,特点及如何阻击通过反射攻击单例,欢迎一起探讨。
网友评论