定义
确保某个类只有一个实例,自行实例化并向整个系统提供这个实例。
介绍
单例模式属于创建类模式。有如下特点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
实现
public class Singleton {
// 在类初始化时,已经自行实例化,所以是线程安全的
private static final Singleton singleton = new Singleton();
// 构造方法为private,防止外部代码直接通过new来构造多个对象
private Singleton() {
}
// 通过getInstance()方法获取实例对象
public static Singleton getInstance() {
return singleton;
}
}
优点:写法简单,线程安全。
缺点:没有懒加载的效果,如果没有使用过的话会造成内存浪费。
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
// 在第一次调用getInstance()时才实例化,实现懒加载,所以叫懒汉式
singleton = new Singleton();
}
return singleton;
}
}
优点:实现了懒加载的效果。
缺点:线程不安全。
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
// 加上synchronized同步
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
优点:实现了懒加载的效果,线程安全。
缺点:使用synchronized会造成不必要的同步开销,而且大部分时候我们是用不到同步的。
public class Singleton {
// volatile 能够防止代码的重排序,保证得到的对象是初始化过
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getSingleton() {
// 第一次检查,避免不必要的同步
if (singleton == null) {
// 同步
synchronized (Singleton.class) {
// 第二次检查,为null时才创建实例
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
优点:懒加载,线程安全,效率较高
缺点:volatile影响一点性能,高并发下有一定的缺陷,某些情况下DCL会失效,虽然概率较小。
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
// 第一次调用getInstance方法时才加载SingletonHolder并初始化sInstance
return SingletonHolder.sInstance;
}
// 静态内部类
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
优点:懒加载,线程安全,推荐使用
public enum Singleton {
// 定义一个枚举的元素,它就是Singleton的一个实例
INSTANCE;
public void doSomething() {
}
}
优点:线程安全,写法简单,能防止反序列化重新创建新的对象。
缺点:可读性不高,枚举会比静态常量多那么一丁点的内存。
// 单例管理类
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
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);
}
}
优点:方便管理。
缺点:写法稍复杂。
注意事项
使用反射能够破坏单例模式,所以应该慎用反射
Constructor con = Singleton.class.getDeclaredConstructor();
con.setAccessible(true);
// 通过反射获取实例
Singleton singeton1 = (Singleton) con.newInstance();
Singleton singeton2 = (Singleton) con.newInstance();
// 结果为false,singeton1和singeton2将是两个不同的实例
System.out.println(singeton1 == singeton2);
可以通过当第二次调用构造函数时抛出异常来防止反射破坏单例,以懒汉式为例:
public class Singleton {
private static boolean flag = true;
private static Singleton singleton = null;
private Singleton() {
if (flag) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被破坏!");
}
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
反序列化时也会破坏单例模式,可以通过重写readResolve方法避免,以饿汉式为例:
public class Singleton implements Serializable {
private static final Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
// 重写readResolve()
private Object readResolve() throws ObjectStreamException {
// 直接返回单例对象
return singleton;
}
}
应用场景
- 频繁访问数据库或文件的对象。
- 工具类对象。
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象。
优缺点
优点:
- 内存中只存在一个对象,节省了系统资源。
- 避免对资源的多重占用,例如一个文件操作,由于只有一个实例存在内存中,避免对同一资源文件的同时操作。
缺点
- 获取对象时不能用new
- 单例对象如果持有Context,那么很容易引发内存泄露。
- 单例模式一般没有接口,扩展很困难,若要扩展,只能修改代码来实现。
网友评论