单例模式介绍
单例模式是应用最广泛的设计模式之一,该模式的目的是为了保证一个类只有一个对象,并对外提供一个全局的访问接口,这样有利于我们协调系统的整体行为。如在一个APP中,应用只有一个ImageLoader实例,ImageLoader包含线程池、网络缓存等等,因此没有必要构造出多个实例,这也是单例模式的使用场景。

单例模式的关键点
1.确保单例类的对象有且只有一个,尤其是在多线程环境下
2.确保单例对象在反序列化时不会重新构建对象
3.构造函数不对外开放,防止外部初始化
4.通过静态方法或者枚举返回单例类对象
单例模式的实现方式
1.饿汉式:最简单的实现方式
/**
* 饿汉式:在定义类的静态私有变量同时进行实例化
* 好处:1.线程安全 2.获取实例速度快
* 缺点:类加载即初始化实例,内存浪费
*/
public class Singleton
{
//私有化单例类对象,类加载时便进行实例化
private static final Singleton singleton = new Singleton();
private Singleton()
{
}
//public的getInstance()方法供外部获取单例实例
public static Singleton getInstance()
{
return singleton;
}
}
2.懒汉式:延时实例化对象,等用着你的时候才去实例化
/**
* 懒汉模式是声明一个静态对象,
* 不同于饿汉式在类加载时就初始化,懒汉模式下,用户第一次调用getInstance时才会进行初始化
*/
public class Singleton
{
private static Singleton singleton ;
private Singleton()
{
}
/**
* 普通实现方式
* 优点:
* 在获取实例的方法中,进行实例的初始化,节省系统资源
* 缺点:
* ①如果获取实例时,初始化工作较多,加载速度会变慢,影响系统系能
* ②每次获取实例都要进行非空检查,系统开销大
* ③非线程安全,当多个线程同时访问getInstance()时,可能会产生多个实例
*/
public static Singleton getInstance1()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
/**
* 改进1:同步锁
* 添加关键字synchronized,保证多线程下对象的唯一性
* 缺点:每次获取实例都要加锁,耗费资源,这种模式不建议使用。
*/
public static synchronized Singleton getInstance2()
{
if (singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
/**
* 改进2:双重检查锁(DCl:Double Check Lock)
* 优点:线程安全,进行双重检查,保证只在实例未初始化前进行同步(DCL模式是使用最多的单例实现方式)
* 缺点:还是实例非空判断,耗费一定资源
*/
public static Singleton getInstance3()
{
//第一次执行getInstance时单例对象才会被实例化,效率高
if (singleton == null)
{
synchronized (Singleton.class)
{
if (singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
}
3.静态内部类单例模式
DCL单例模式虽然在一定程度上解决了资源消耗、多余同步、线程安全问题,但是由于Java内存模型的原因偶尔还会失败,另外在高并发的情况下也有一定的缺陷,建议使用静态内部类的方式来实现单例:
/**
* 静态内部类单例模式
* 第一次加载Singleton类时不会进行初始化,只有调用getInstance时才会
* 另外在getInstance方法中会加载SingletonHolder类,这种方式不仅线程安全也保证了对象的唯一性(类似于饿汉,但没有在类加载的时候初始化实例对象)
*
*
*/
public class Singleton
{
private Singleton()
{
}
public static Singleton getInstance()
{
return SingletonHolder.singleton;
}
private static class SingletonHolder
{
private static final Singleton singleton = new Singleton();
}
}
4.枚举单例
前面三种方式都可以实现单例,但是或多或少的都会出现一些问题或者比较繁琐,而枚举可以通过最少最简单的代码实现单例。另外有一种情况下它们还是会出现实例化对象的情况,那就是反序列化。
通过序列化可以将对象写入磁盘,然后再读回来,即使构造函数是私有的,反序列化依然可以通过特殊的途径去创建一个新的实例。反序列化提供了了一个特别的钩子readResolve(),这个方法可以让开发人员控制对象的反序列化,上述的三种实例,如果要杜绝反序列化时生成新的实例那么需要加入以下方法:
private Object readResolve() throws ObjectStreamException
{
return sInstance;
}
使用枚举可以解决以上所有问题:
/**
* 枚举单例
* 优点:
* 1.枚举的创建是线程安全的
* 2.任何情况下都是单例
* 2.杜绝单例对象被反序列化时生成新的对象
*
*/
public enum Singleton
{
INSTANCE;
public void doSth()
{
System.out.println("do something .");
}
}
单例模式管理类
在程序开发中如果用到了多个单例对象,最好将单例类型注入到一个统一的管理类中,再使用key获取响应的单例实例。这种方式通过统一的接口进行管理,降低了用户的使用成本,也对用户隐藏了具体实现,降低耦合度。
public class SigletonManager
{
private static Map<String,Object> objMap = new HashMap<String,Object>();
private SingletonManager()
{
}
public static void registerService(String key,Object obj)
{
if(!objMap.containsKey(key))
{
objMap.put(key,obj);
}
}
public static Object getService(String key)
{
return objMap.get(key);
}
}
总结
不管使用哪种单例模式,它们的核心都是将构造函数私有化,通过静态方法获取唯一实例,并且在这个过程中要保证线程安全和防止反序列化生成新的实例对象。选择哪种方式需要根据项目本身的条件,适用才是最好的。
网友评论