概念
保证java虚拟机范围内只有一个实例,并提供该实例的一个全局访问点。关键是一下几点:
- 私有构造函数
- 静态方法或枚举返回单例类对象
- 确保在多线程环境下也只有一个对象
- 确保在反序列化时不会重新创建对象。
优点
- 减少内存开支(特别是一个对象须频繁创建销毁时)和性能开销
- 避免多重占用资源(比如将写操作封装为一个单例可以避免对同一个资源文件的同时写)
- 优化共享资源的访问(单例模式有全局访问点)
缺点
- 没有接口,扩展困难(只能修改代码)
- 单例模式若持有Context容易引发内存泄漏(所以尽量传的是Application Context)
实现方式
- 饿汉式
线程安全,类加载时创建好了,以空间换时间
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
- 懒汉式
延迟加载和缓存,以时间换空间。有synchronized关键字保证线程安全。不过每次取得实例都要进行同步造成了额外开销。
private static Singleton instance = null;
private Singleton(){}
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
- Double CheckLock
资源利用率高,只在需要时才初始化对象,第一次读取才进行同步,使用最多的单例模式,但在高并发情况下也有一定缺陷。
private volatile static Singleton instance = null;//保证从主内存读取instance对象,保证获取到正确的对象
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){ //第一次:用来判断只在第一次时执行下面的代码,后面有了直接返回,避免创建不必要同步,
synchronized(Singleton.class){
if(instance == null){ //第二次:进入同步块后检查在null的情况下创建实例。避免由于CPU时间片切换创建多个对象
instance = new Singleton();
}
}
}
return instance;
}
注意instance = new Singleton()并非原子操作,它分为以下三步
1 给 Singleton的实例分配内存 2 调用Singleton的构造函数,初始化成员字段 3 将instance对象指向分配的内存空间(此时instance才不为null)
由于JVM存在指令重排序,实际执行顺序可以123或132,若是132会导致多线程下DCL失效,所以需要加上volatile保证从主内存读取,这样所有线程获取到的对象都是已经初始化好的
- 静态内部类
利用了java外部类访问内部类私有成员的机制,只在第一次调用getInstance()时才会导致instance初始化(虚拟机装载SingletonHolder),线程安全。值得推荐使用。
Java语言规范说了enclosing class可以访问inner class的private/protected成员,inner class也可以访问enclosing class的private/protected成员。编译器实现的时候是这样的:enclosing class和inner class不再是嵌套结构,而是变为一个包中的两个类,然后,对于private变量的访问,编译器会生成一个accessor函数.
内部类的分类
- 类级别(static):相当于外部类的static成员,其对象与外部类对象实例不存在依赖性。可直接创建。只在其第一次使用时才装载类。
- 对象级别:必须绑定在外部类对象实例上
private Singleton(){}
public static Singletonm getInstance(){
return SingletonHolder.instance;
}
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
- 枚举单例
写法简单,在任何情况下(包括多线程和反序列化)都是一个单例
public enum SingletonEnum{
INSTANCE;
public void fun(){
//do something
}
}
上面所有单例的实现方式,若要防止反序列化时重新生成对象,则都需要加入以下方法
private Object readResolve() throws ObjectStreamException() {
return instance;
}
- 使用容器
将多种单例类型注入到一个统一的管理类(Singleton)中,可以管理多种类型的单例。降低耦合度
public class Singleton{
private static Map<String, Object> map = new HashMap<String, Object>();
private Singleton(){}
public static void resgisterService(String key, Object instance){
if(!map.containsKey(key)){
map.put(key, instance);
}
}
public static Object getService(String key){
return map.get(key);
}
}