使用模式最好的方式是:“把模式装进脑子里,然后在你的设计和已有的应用中,寻找何处可以使用它们。”
-
使用场景:
a. 产生莫对象会消耗过多的资源,为避免频繁的创建和销毁对象对资源造成的浪费。例如:对数据库的操作、访问IO、线程池、网络请求等。
b. 某种类型的对象应该有且只有一个。若制造出多个这样的实例,可能导致:程序行为异常、资源使用过量、结果不一致等问题。如果多人能同时操作一个文件,又不进行版本管理,必然会有的修改被覆盖,所以:
一个系统只能有:一个窗口管理器或文件系统,计时工具或 ID(序号)生成器,缓存(cache),处理偏好设置和注册表(registry)的对象,日志对象。
- 优点:减少系统开支、减少系统性能开销、避免对资源的多重占用和同时操作。
- 缺点:扩展困难、易引发内存泄漏、测试困难、一定程度上违背单一职责原则,进程被杀时可能有状态不一致的问题。
- 实现方式:
按加载时机分为饿汉模式和懒汉模式
按实现方式分为双重检查加锁、内部类和枚举方式等 - 定义:
构造函数对其他类不可见,仅仅提供一个获取唯一实例的静态方法,并且必须保证这个获取实例的方法时线程安全的,并防止反序列化、反射、克隆、多个类加载器、分布式系统等多种情况下重新生成新的实例对象。 -
具体实现方案:
懒汉模式
双重检测锁定方式:
public class Singleton { private static volatile Singleton instance; private Singleton(){} public static Singleton getInstance(){ if (instance==null){ synchronized (Singleton.class){ if (instance==null){ instance=new Singleton(); } } } return instance; } }
该方式在1.5以下会失效。
静态内部类:
public class Singleton { private Singleton(){} public static final Singleton getInstance(){ return SingletonHolder.INSTANCE; } public static class SingletonHolder{ private static final Singleton INSTANCE=new Singleton(); } }
饿汉模式
public class Singleton { private Singleton(){} private static final Singleton instance=new Singleton(); public static Singleton getInstance(){ return instance; } }
饿汉与懒汉的比较:
饿汉在类加载时就已经实例化了静态对象,所以第一次调用时更快,但是在程序运行期间会一直占据一定的内存;懒汉是延时加载,优势在于资源利用率高哦,但第一次调用时的初始化工作会导致性能延时,并且在后面每次获取实例时也要先判断实例是否被初始化,造成效率损耗。
登记式单例
在我们的程序中,随着迭代版本的增加,代码也越来越复杂,往往会使用到多个处理不同业务的单例,这时我们就可以采用 Map 容器来统一管理这些单例,使用时通过统一的接口来获取某个单例。在程序的初始,我们将一组单例类型注入到一个统一的管理类中来维护,即将这些实例存放在一个 Map 登记薄中,在使用时则根据 key 来获取对象对应类型的单例对象。对于已经登记过的实例,从 Map 直接返回实例;对于没有登记的,则先登记再返回。从而在对用户隐藏具体实现、降低代码耦合度的同时,也降低了用户的使用成本。
public class SingletonManager { private static Map<String,Object> objectMap=new HashMap<>(); private SingletonManager(){} public static void registerService(String key,Object instance){ if (!objectMap.containsKey(key)){ objectMap.put(key, instance); } } public static Object getService(String key){ return objectMap.get(key); } }