常见设计模式-单例模式(1)
定义:确认任何情况下都绝对只有一个实例
优点:
在内存中只有一个实例减少了内存开销。
可以避免对多资源的多重占用
设置全局访问点,严格控制访问。
缺点:
没有接口,扩展困难。
如果要扩展单例对象,只有修改代码,没有其他途径。
不符合开闭原则。
步骤:
私有化构造器
保证线程安全
延迟加载
防止序列化和反序列化
防御反射攻击单例
例如:ServletContext、ServletConfig、ApplicationContext、DBpool等类
常见写法: 1、饿汉式单例
2、懒汉式单例
3、注册式单例
饿汉式单例:
定义:在单例类首次加载时就创建单例。不管用不用,先吃饱在说。
缺点:浪费内存空间。
懒汉式单例
定义:被外部类调用时才创建单例
因为线程的随机性,会抢占CPU,所以会引发线程安全问题。
情况1:
当线程1实例化完,产生对象 @534后,然后线程0才进入该方法中
那么此时,lazy已经实例化完毕,对象是唯一的,没有线程安全问题。
情况2:
当线程1 和线程0 同时处于该方法中,并且都没有赋值。
虽然线程1赋值
产生对象 @535
然后线程0也执行赋值操作
可以看到 线程0覆盖之前对象实例 产生不同的对象实例为 @536
随后两个线程同时打印出该对象内存地址为 一模一样,实际上是后面线程创建的对象实例覆盖了前面对象的实例。
情况三:
将线程1全部执行完,然后产生 一个对象实例,也没有被第二个线程覆盖。
随后才执行线程0,可以发现产生了两个不同的对象实例。
解决方案 将该方法同步
这时可以看到,线程1可以运行,而线程0处于监听状态,等待线程1运行完毕。
线程1运行完毕后,产生一个对象实例,线程0运行到该方法时,已经有一个实例会直接返回。
虽然JDK1.6之后对synchronized 优化了不少,但是不可能避免还是会有性能问题。可能会锁住整个类。
解决方案 采用双捡锁,最多只会锁住整个方法
优化方案如下:
但是该单例方式性能并不能达到很高的效率
解决方案如下:采用内部类单例模式
优点:1、全程没有用到synchronized关键字,性能高。
2、运用类加载机制,首先外部类被加载的时候,会先加载内部类,其次再次去调用getInstance() 方法时才会去加载内部类的逻辑。
3、运用JVM底层执行逻辑,完美的避免了线程安全问题
缺点:虽然构造方法被私有了,但是还是可以被反射攻击。虽然不会刻意去攻击,但是有可能会被误用。
缺点如下:
解决被反射方案如下:
但是还有一种方式可以破坏该单例 :序列化
如下所示:
解决方案:重写readResolve()方法
该方法作用是:再反序列化读取该二进制文件时,如果有这个方法存在,那么会将值赋予该类的实例。