设计模式之单例模式
单例,顾名思义,就是指在系统的运行过程中某一个类只有唯一一个对象存在。单例模式,就是实现这样一种方式的一个设计模式。
常见的单例场景
- windows系统的任务管理器、回收站
- Servlet中的ServletContext
- spring中的默认bean对象
优势
- 由于系统中只存在一个该类型的对象,减少系统的开销,特别是当创建该对象所消耗的资源比较多,但是内容却是一样的时候,单例的优势就非常明显了。
- 作为全局的资源访问点,减少出错的概率,尤其是在并发环境下
常见的单例写法
所有的单例的写法都有非常明显的特征,那就是构造器私有化,就是防止对象被其他类创建。由类自己创建对象,并且对外提供获得该对象的方法,从而实现单对象,根据创建对象方法的不同,可以有以下几种表现形式
饿汉形式
所谓的饿汉形式,其实就是指对象在类被加载的时候就被创建,具体过程如下所示。
/**
* 单例模式的饿汉形式
* @author xuhuanfeng
*
*/
class TaskManager{
// 私有化构造器
private TaskManager(){}
// 静态属性,在类被加载的时候就会进行初始化,也就是创建该对象
private static TaskManager taskManager = new TaskManager();
// 对外的访问方法,用于获得对象
public static TaskManager getInstance(){
return taskManager;
}
}
对应的测试类如下
@Test
public void testTaskManager() {
TaskManager taskManager1 = TaskManager.getInstance();
TaskManager taskManager2 = TaskManager.getInstance();
System.out.println("obj one " + taskManager1);
System.out.println("obj two " + taskManager2);
}
输出的结果为
obj one cn.xuhuanfeng.sigleton.TaskManager@15db9742
obj two cn.xuhuanfeng.sigleton.TaskManager@15db9742
从上面我们可以看到,类TaskManager仅产生一个对象。
饿汉方式看起来非常直观,代码书写起来也非常方便,但是,这种方式的优缺点却非常明显
优点:由于类被加载的时候对象就已经创建好了,具有天然的线程安全性
缺点:当不需要该类的时候,系统依旧会加载该类,造成资源的浪费,也就是无法做到延迟加载
懒汉形式
所谓的懒汉形式,就是指当需要对象的时候才创建对象,也就是延迟加载,具体过程如下所示
class TaskManager {
// 私有化构造器
private TaskManager() {
}
// 静态属性,但是这里不进行初始化,也就是加载的时候其值会默认为null
private static TaskManager taskManager;
// 由于调用方法的时候,可能会发生并发现象,所以这里需要使用synchronized进行锁定
public static synchronized TaskManager getInstance() {
if (taskManager == null) {
taskManager = new TaskManager();
}
return taskManager;
}
}
对应的测试类同上,这里省略不写
输出的结果如下所示
obj one cn.xuhuanfeng.sigleton.TaskManager@5010be6
obj two cn.xuhuanfeng.sigleton.TaskManager@5010be6
懒汉方式看起来跟饿汉形式非常接近,主要区别在于对象创建的时间,也正是由于这个不同,其优缺点也比较明显
- 优点:只有当需要的时候才会创建对象,做到了延迟加载
- 缺点:为了保证获得的对象唯一,必须对访问的方法进行加锁,会带来一定的资源消耗
静态内部类形式
所谓的静态内部类是指,利用静态内部类只有在被调用的时候才会被加载的特性,同时,类的静态属性在类被加载时会自动初始化,实现延迟加载,具体过程如下
class TaskManager {
// 私有化构造器
private TaskManager(){}
// 私有的静态内部类
private static class Task {
// 私有的静态属性,当被加载时会自动初始化
private static TaskManager taskManager = new TaskManager();
}
// 对外的访问方法,用于获得对象
public static TaskManager getInstance() {
return Task.taskManager;
}
}
对应的测试类同上,这里省略不写
结果如下所示
obj one cn.xuhuanfeng.sigleton.TaskManager@5010be6
obj two cn.xuhuanfeng.sigleton.TaskManager@5010be6
可以看到,通过这种方式,可以实现单例对象,虽然思路比较绕,但是这种实现方式的优势比前面两个明显
- 优点:实现延迟加载,同时由于是在加载时进行初始化,所以具有天然的线程安全性
- 缺点:实现思路比较绕,理解起来比较难
枚举类形式
枚举类的实现,不过这种方面目前笔者只知道其写法,还未理解其原理,所以无法做其他解释,实现代码如下
/**
* 单例模式的枚举类型实现
* @author xuhuanfeng
*
*/
enum TaskManager{
INSTANCE;
// 可以增加其他方法
}
测试类同上
可以看到,这种方式非常简便,而且据网友的介绍,这种方式是比较好的一种方式,笔者正在努力弄懂。
今天学习了Enum类型之后,对上面的定义方式也比较清楚了,上面定义的方式实际上是利用Enum类型所定义出来的对象本身就是静态常量的特性,而且是对应该类型的一个对象,而且可以根据需要增加新方法等实现的,关于Enum可以参见笔者的Enum学习笔记
总结
本节介绍了单例模式及其作用,以及常见的实现单例模式的几种方式,其中静态内部类以及枚举类是实现单例模式比较好的两种做法,也是比较推荐的做法。