1.定义
- 一个类只有一个实例,并且只有一个全局获取入口。
2.适用场景
- 某个实例对象频繁被访问。
- 某个实例占用的资源较多。
3.实现方式
3.1 懒汉模式(线程不安全)
这样可能会出现线程不同的方法,所以必须对getSingleton方法进行同步。
public class Singleton {
private static Singleton singleton;//私有静态变量
private Singleton(){};//私有构造方法
//全局静态访问入口
public static Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
3.2 懒汉模式(线程安全)
public class Singleton {
private static Singleton singleton;//私有静态变量
private Singleton(){};//私有构造方法
//全局静态访问入口
public static synchronized Singleton getSingleton(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
这样子线程就安全了,但是消耗了不必要的同步资源,不推荐这样使用。
3.3 饿汉模式(线程安全)
public class Singleton {
private static Singleton singleton=new Singleton();//私有静态变量
private Singleton(){};//私有构造方法
//全局访问入口
public Singleton getSingleton(){
return singleton;
}
}
避免线程安全问题,在声明私有静态变量时,就已经实例化了类,不用考虑线程同步问题。
3.4 DCL模式(Double CheckLock)
public class Singleton {
private static Singleton singleton;//私有静态内部变量
private Singleton(){};//私有构造方法
//全局访问入口
public static synchronized Singleton getSingleton(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
通过两个判断,第一层是避免不必要的同步,第二层判断是否为null。
可能会出现DCL模式失效的情况。
DCL模式失效:
singleton=new Singleton();这句话执行的时候,会进行下列三个过程:
- 分配内存。
- 初始化构造函数和成员变量。
- 将对象指向分配的空间。
由于JMM(Java Memory Model)的规定,可能会出现1-2-3和1-3-2两种情况。
所以,就会出现线程A进行到1-3时,就被线程B取走,然后就出现了错误。这个时候DCL模式就失效了。
Sun官方注意到了这种问题,于是就在JDK1.5之后,具体化了volatile关键字,这时候只用调整一行代码即可。
private volatile static Singleton singleton;
3.5 静态内部类模式
public class Singleton {
private Singleton(){}
public static Singleton getSingleton(){
return SingletonHolder.singleton;
}
private static class SingletonHolder{
private final static Singleton singleton=new Singleton();
}
}
第一次加载Singleton类不会加载SingletonHolder类,但是调用getSingleton时,才会加载SingletonHolder,才会初始化singleton。即确保了线程安全,又保证了单例对象的唯一性,延迟了单例的实例化。这是最推荐的方式。
3.6 枚举模式
public enum Singleton{
singleton;
public void hello(){
System.out.print("hello");
}
}
这样很简单,线程时安全的,并且避免了序列化和反射攻击。
除了枚举模式,其他模式在实现了Serializable接口后,反序列化时单例会被破坏。所以要重写readResolve()方法。
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
3.7 使用容器实现单例模式
public class SingletonManger{
private static Map<String,Object> objectMap=new HashMap<String,Object>();
private SingletonManger(){}
public static void registerService(String key,Object singleton){
if(!objectMap.containsKey(key)){
objectMap.put(key,singleton);
}
}
public static Object getObjectService(String key){
return objectMap.get(key);
}
}
这样可以将多个单例对象注入到HashMap中,进行统一管理,更加方便快捷。