一.单例模式的优缺点:
单例模式优点:
1.值在内存中只有一个实例,减少内存开销。
2.可以避免对资源的多重占用。
3.设置全局访问点,严格控制访问。
单例模式的缺点:
1.没有接口,不易于扩展
2.如果要扩展只能修改单例对象,违反开闭原则
二.单例模式的实现:
1.饿汉式单例
特点:将实例在静态代码块中实现,类加载时就创建。
缺点:浪费内存空间。
2.懒汉式单例
初始化时不加载,在试用时才加载。
缺点:线程不安全
安全版饿汉式单例:
3.加synchronized关键字
缺点:锁太重,造成整个系统死锁
4.doublecheck双重锁
看似没有问题,但是single1=new Single1();这一步,不是原子操作,可能存在指令重排,所以就要用volatile。volatile的作用是让指令具有可见性,防止指令重排,所以加上volatile才是真正的doublecheck的实现
那么有没有不使用synchronized的方法呢
5.静态内部类式单例
利用静态内部类的特性,被调用时才执行生成实例,JVM层面避免了线程安全问题。性能最优,但虽然解决了线程安全问题,但还是会被反射攻击。
利用反射测试一下。返回结果false
那么可以在私有构造器上直接抛异常,强制不能构造
那么问题又来了,序列化的方式也能破坏单例
可以看出序列化输入输出后,实例发生了变化。
稍作修改后,再次测试,发现返回了true。readResolve(return instance;)这个方法起了作用。原来是序列化的时候,会生成一个新的对象,但是在最后返回之前,会检查是否有readResolve这个方法,如果有,则用readResolve()方法返回的值,而原本在序列化过程中生成的新的实例则会被JVM回收。这样就能做到即使被序列化也能保证只返回一个单例。
那么有没有既能保证线程安全,又能不这么麻烦的避免序列化和反射的攻击呢?
6.注册式单例:
利用枚举特性,从JDK层面枚举方式的特性可以不被序列化,而枚举本身就是不可以通过构造创建实例天然的创造了单例的环境,所以不会被反射攻击。
以上为单例模式的学习与总结。