单例模式-Java设计模式笔记(五)

单例模式是保证一个类仅有一个实例,并提供一个全局访问点。属于创建型,也属于GOF23种设计模式

适用范围

想确保任何情况下都绝对只有一个实例。

优缺点

优点:在内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用;设置全局访问点,严格控制访问。

缺点:没有接口,扩展困难。

示例

懒汉式解决方案

我们可以使用 synchronized 锁来实现线程安全。

方式一:但是在 static 方法中使用 synchronized 锁是针对整个类的,是一个类锁(全局锁),锁的范围比较大,导致加锁解锁资源开销也比较大。下面示例中 getInstance1getInstance2 等同。

方式二:使用 volatile 关键字来“禁止指令重排序”,并采用双重检查方式,减少 synchronized 加锁解锁的次数,从而解决效率和线程安全问题。

注意:在上面示例中,创建对象并不是直接完成的。事实上会先分配对象的内存空间,然后初始化对象并让 lazyDoubleCheckSingleton 指向内存空间,最后才被线程访问。其中初始化对象和指向对象的内存空间并不是一定按顺序执行的,这样在多线程环境下就有可能发生对象还未初始化,就被其他线程访问了,从而导致异常发生。

所以我们可以使用 volatile 关键字来保证创建对象过程中不会出现重排序,从而避免这个问题。

方式三:基于类初始化的延迟加载,我们可以使用静态内部类的方式实现。在多线程环境中,当一个线程访问这个静态内部类,会添加一个 Class 对象的初始化锁( InnerClass 对象的初始化锁),让这个静态内部类初始化时,对象实例化过程中的重排序对其他线程不可见。

饿汉式解决方案

方式一:在类加载的时候就直接创建单例对象。

方式二:通过静态代码块创建单例对象。

序列化和反序列化

我们自己创建的类,实现 Serializable 接口,就能够实现序列化和反序列化。但是会导致单例对象被破坏,造成对象不是同一个,我们可以通过实现 readResolve() 方法解决这个问题。

Test.java 测试类:

运行结果:

反射攻击

六阿哥

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: