目录
定义
其定义如下:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类 只有一个实例,而且自行实例化并向整个系统提供这个实例。)
再来查看一个通用类图如下:
Singleton类提供一个静态Singleton变量并实例化自己,将无参构造函数给私有化,避免外部类可以new一个新对象出来,然后提供一个getSingleton()方法提供给到外部,该方法里只提供自己已实例化好的对象,将这个通用类再对应到上面的定义,就应该基本能理解啥是单例模式了。再将抽象转换三个特点理解如下:
- 自己实例化一个实例
- 将构造方法私有化,避免别人可以实例化
- 创建public方法提供已实例化的这个对象
懒汉式与饿汉式
啥是饿汉式,啥是懒汉式,用最简单的话来说,饿汉式是应用程序一启动,该实例对象就已被实例化,懒汉式是被调用的时候才被实例化,如果一直都不被调用,那么该实例不会被实例化,好处还是有一点,那么分别再通过代码来认识下吧。
static class HungrySingleton{
// 自实例化
private static final HungrySingleton hungrySingleton = new HungrySingleton();
// 将构造方法私有化
private HungrySingleton(){}
// 提供获取实例的方法
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
这里看到的是饿汉式,应用进程启动后该对象就会被实例化。
static class LazySingleton{
private static LazySingleton lazySingleton = null;
// 将构造方法私有化
private LazySingleton(){}
// 提供获取实例的方法
public static synchronized LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
这里看到的就是懒汉式,该模式有个缺点,每次get实例,都会被加锁变成同步,在高并发场景下式比较影响效率的,而且其实实例化只会一次,理论上加锁一次即可,那么有什么处理方法呢?
static class LazySingleton2{
/**
* 要添加volatile关键字,该关键字有两个作用,1、保证线程操作能够互相看到,2、保证按顺序执行
* singleton = new Singleton();
* 该语句非原子操作,实际是三个步骤。
* 1.给singleton分配内存;
* 2.调用 Singleton 的构造函数来初始化成员变量;
* 3.将给singleton对象指向分配的内存空间(此时singleton才不为null);
* 由于Java 平台内存模型的原因,允许无序执行,所以可能执行的顺序为1、3、2,如果先执行了3那么另外一个
* 线程判断不为null,返回的该实例其实式未初始化成员变量的,那么就会导致不可用,所以加volatile
* 可以避免这个问题
*/
private static volatile LazySingleton2 lazySingleton2 = null;
// 将构造方法私有化
private LazySingleton2(){}
// 提供获取实例的方法
public static synchronized LazySingleton2 getInstance(){
if(lazySingleton2 == null){
synchronized (LazySingleton2.class){
if(lazySingleton2 == null){
lazySingleton2 = new LazySingleton2();
}
}
}
return lazySingleton2;
}
}
这里看到的就是懒汉式的变种,双重校验,每次先判断是否为null,如果为null才会加synchronized同步处理,后续实例化后不会再加锁处理,高并发场景下也不会受到影响,但需要注意的式需要加volatile关键字,具体原因见注释。
还有静态内部类实例方式,枚举类方式,这里就不再多做介绍。
应用场景
在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”,可以采用单例模式,具体的场景如下:
- 要求生成唯一序列号的环境
- 在整个项目中需要一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用把每次刷新都记录到数据库中,使用单例模式保持计数器的值,并确保是线程安全的
- 频繁访问数据库或文件的对象,如配置文件对象
- 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
- Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?
优缺点
优点:
1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性
3.提供了对唯一实例的受控访问
4.由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
5.允许可变数目的实例(单例的拓展)
6.避免对共享资源的多重占用
缺点:
1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态
2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难
3.单例类的职责过重,在一定程度上违背了“单一职责原则”
4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失
使用注意事项:
1.使用时不能用反射模式创建单例,否则会实例化一个新的对象
2.使用懒单例模式时注意线程安全问题
3.饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
总结
在了解了单例模式基本概念后,我们可以在读源码的过程中,有意识的去注意哪些用到了单例模式,然后思考为什么要用?通过这种方式去进一步熟悉它。在工作过程中尝试使用它,多多思考不同场景什么时候该用,什么时候不该用。最后熟练后忘记它就是融入骨子里的东西了。