单例模式
定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
应用场景
当需要保证类在虚拟机中的对象唯一性
创建多个实例浪费资源
多个实例由于多次调用而出现错误。
一般写工具类,线程池,缓存,数据库等可以使用
特点
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
设计思想
- 将构造方法限定为private避免了类在外部被实例化。
- 单例类中持有一个私有本类实例的引用。
- 定义一个公共的方法getInstance(),在同一个虚拟机范围内,Singleton的唯一实例只能通过该方法访问。
- 注意:事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效,此问题在此处不做讨论。
实现方式
一、懒汉式单例
public class Singleton {
private Singleton() {}
private static Singleton single = null;
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。
1、在getInstance方法上加同步
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
2、双重检查锁定
public class Singleton {
private Singleton() {}
private static volatile Singleton single = null;
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
3、静态内部类
public class Singleton {
private Singleton (){}
public static final Singleton getInstance() {
return LazyHolder.INSTANCE;
}
// 静态内部类
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
和饿汉式类似,两者都是通过类装载机制来保证初始化实例的时候只有一个线程,从而避免线程安全问题。饿汉式的Singleton类被加载,就会实例化Singleton,而静态内部类这种,当Singleton类被加载时,不会立即实例化,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。
二、饿汉式单例
public class Singleton {
private Singleton() {}
private static final Singleton single = new Singleton();
public static Singleton getInstance() {
return single;
}
}
饿汉式在类加载的同时就已经实例化一个静态的对象供系统使用,所以天生是线程安全的。但是若没有使用到该实例,依然会加载,从而造成资源浪费。
三、枚举实现单例模式
public enum SingletonEnum {
INSTANCE;
private Singleton instance;
SingletonEnum() {
instance = new Singleton()
}
public Singleton getInstance() {
return instance;
}
}
访问方式:SingletonEnum.INSTANCE.method();
INSTANCE即为SingletonEnum类型的引用,得到它就可以调用枚举中的方法。既避免了线程安全问题,还能防止反序列化重新创建新的对象,但是失去了类的一些特性,没有延时加载,推荐使用。
四、使用容器实现单例模式
public class SingletonManager {
private static Map<String,Object> objMap = new HashMap<String,Object>();
private Singleton() { }
public static void registerService(String key,Object instance) {
if(!objMap.containsKey(key)) {
objMap.put(key,instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}
将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用 户隐藏了具体实现,降低了耦合度。
优缺点
优点:保持类对象唯一性,对于频繁创建和销毁的对象可以提高性能。
缺点:扩展困难,单例的方法无法生成子类对象,要扩展的话基本要重写这个类。