曾经天真的以为单例只有懒汉和恶汉两种!原来单例模式还能被破解!!!
01-单例设计模式
第一章:单例模式核心作用
(1)保证一个类只能有一个实例(一个对象)
(2)并且提供一个供外界访问该实例的全局访问点
第二章:常见应用场景
(1)windows的任务管理器、回收站
(2)项目中,读取配置文件的类,一般只有一个对象。没必要每次使用配置文件的数据都要new一个对象去读取
(3)网站的计数器,一般采用单例模式设计,否则无法做到同步
(4)数据库的连接池设计,一般使用单例模式设计
(5)在spring中,每个bean默认就是单例模式,这样做的优点是spring容器方便管理
(6)在servlet编程中,每个servlet也是单例模式
第三章:单例模式的优点
(1)减少系统性能的开销:当一个对象的产生需要比较多的资源时,如需要读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久存留在内存中的方式来解决。
(2)可以在系统设置全局的访问点。优化共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
第四章:常见的单例模式实现方式及优缺点
4.1:饿汉式(重点)
特点:线程安全、调用效率高;但是,不能延时加载
在系统启动的时候,加载该类的时候,由于static的原因,会立即去加载该类的实例化,同时也由于是static,该类在内存中只有这一个,达到单例的要求。但是,由于是立即加载,会占用系统存储空间,有一定的缺陷。
由于将无参构造器私有化,所有外界想要使用该类,必须通过提供的全局唯一访问点,拿到该类的实例化对象。不管外界获取了多少次对象,在内存中,该类的实例对象只有一个。
public class singletondemo02 { private static /*final*/ singletondemo02 s = new singletondemo02(); private singletondemo02(){} // 私有化构造器 public static /*synchronized*/ singletondemo02 getinstance(){ return s; } }
4.2:懒汉式(重点)
特点:线程安全、调用效率不高;但是,可以延时加载
在系统启动的时候,加载该类时,并不会立即去初始化该类;
而是在调用该类的getinstance()方法时,判断当前类是否被创建,如果没有被创建,则进行创建对象,返回。如果已经创建了,直接返回对象。
同时,考虑到并发的情况下,需要使用synchronized,保证每次只有一个线程去访问该类的方法。保证实例化对象的唯一性。
这种方式,属于延迟加载,真正用到该类的时候才会去加载。提高了资源利用率,但是调用getinstance()方法都要同步,并发效率低下。
public class singletondemo01 { private static singletondemo01 s; private singletondemo01(){} // 私有化构造器 public static synchronized singletondemo01 getinstance(){ if(s==null){ s = new singletondemo01(); } return s; } }
4.3:双重检测锁式(了解)
特点:由于jvm底层内部模型原因,偶尔会出问题,不建议使用
这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。
public class singletondemo03 { private static singletondemo03 instance = null; public static singletondemo03 getinstance() { if (instance == null) { singletondemo03 sc; synchronized (singletondemo03.class) { sc = instance; if (sc == null) { synchronized (singletondemo03.class) { if(sc == null) { sc = new singletondemo03(); } } instance = sc; } } } return instance; } private singletondemo03() { } }
4.4:静态内部类式(理解)
特点:线程安全、调用效率高;但是,可以延时加载
将实例化操作放到静态内部类中,外部类没有static,所以,在初始化类的时候,不会立即去加载静态内部类,当然也不会去实例化对象,达到了延迟加载的特性。
只有在调用了getinstance()方法的时候,才会去加载静态内部类。加载类的时候,线程是安全的。
instance是static final修饰的,保证了全局唯一性,即单例性。
兼备并发高效率调用和延迟加载特性。
public class singletondemo04 { private static class singletonclassinstance { private static final singletondemo04 instance = new singletondemo04(); } public static singletondemo04 getinstance() { return singletonclassinstance.instance; } private singletondemo04() { } }
4.5:枚举单例(理解)
特点:线程安全、调用效率高,不能延时加载
枚举本身就是单例模式。由jvm从根本上提供保障!避免通过反射和反序列化的漏洞!
public enum singletondemo05 { /** * 定义一个枚举的元素,它就代表了singleton 的一个实例。 */ instance; /** * 单例可以有自己的操作 */ public void singletonoperation(){ // 功能处理 } }
第五章:通过反射和反序列化破解单例模式(除枚举单例)
5.1:通过反射破解单例模式(除枚举单例)
//通过反射的方式直接调用私有构造器 class<singletondemo6> clazz = (class<singletondemo6>) class.forname("com.bjsxt.singleton.singletondemo6"); constructor<singletondemo6> c = clazz.getdeclaredconstructor(null); c.setaccessible(true); singletondemo6 s3 = c.newinstance(); singletondemo6 s4 = c.newinstance(); system.out.println(s3); system.out.println(s4);
如何避免反射破解单例?
通过在构造方法中手动抛出异常
private singletondemo6(){ //私有化构造器 if(instance!=null){ throw new runtimeexception(); } }
5.2:通过反序列化破解单例模式(除枚举单例)
//通过反序列化的方式构造多个对象 fileoutputstream fos = new fileoutputstream("d:/a.txt"); objectoutputstream oos = new objectoutputstream(fos); oos.writeobject(s1); oos.close(); fos.close(); objectinputstream ois = new objectinputstream(new fileinputstream("d:/a.txt")); singletondemo6 s3 = (singletondemo6) ois.readobject(); system.out.println(s3);
如何避免反序列化破解单例模式?
表示:在反序列化的时候,如果已经定义了readresolver()方法,则直接返回此方法指定的对象,而不需要单独再创建新对象。
private object readresolve() throws objectstreamexception { return instance; }
第六章:五种单例模式的效率问题
6.1:五种单例模式在多线程环境下的测试效率(相对效率)
countdownlatch类:
同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或者多个线程一直等待。
countdown():当前线程调用此方法,则计数减一,减一放在finally中执行
await():调用此方法会一直阻塞当前线程,直到计数器为0;
public static void main(string[] args) throws exception { long start = system.currenttimemillis(); int threadnum = 10; final countdownlatch countdownlatch = new countdownlatch(threadnum); for(int i=0;i<threadnum;i++){ new thread(new runnable() { @override public void run() { for(int i=0;i<1000000;i++){ // object o = singletondemo4.getinstance(); object o = singletondemo5.instance; } countdownlatch.countdown(); } }).start(); } countdownlatch.await(); //main线程阻塞,直到计数器变为0,才会继续往下执行! long end = system.currenttimemillis(); system.out.println("总耗时:"+(end-start)); }
第七章:五种单例模式的选择
(1)单例对象,占用资源少,不需要延迟加载
枚举式优于饿汉式
(2)单例对象,占用资源大,需要延时加载
静态内部类式优于懒汉式
上一篇: numpy 对矩阵中Nan的处理:采用平均值的方法
下一篇: Dubbo从入门到实战:实战篇