单例模式是确保一个类最多只有一个实例,并提供一个全局访问点。就像你是你们家的独生子,一旦邻居跟你爸告状说“你家孩子打架了”,说的只可能是你。
单例模式有两种创建方式:懒汉式和饿汉式。在下文会对其进行详细解说。
使用场景和优缺点 |
使用场景
(1)网站计数器。当需要统计当前在线人数,只能用一个全局对象来记录。
(2)应用程序的日志。日志内容一般为共享操作,需要在后面不断写入内容,所以需要单例模式。
(3)线程池或数据库连接池。
(4)操作系统的任务管理器、回收站、文件系统等,都必须确保只有一个。
优缺点
(1)优点:
减少内存开销,尤其是频繁的创建和销毁实例;
避免对资源对过多占用。
(2)缺点:
不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态;
单例类的职责过重,在一定程度上违背了“单一职责原则”。
实现 |
实现思路
总体来说是:单例类自己给自己创建唯一的实例,如果外面对象需要此实例,它可以提供。实现思路如下:
(1)让构造函数被private修饰,则类外就无法利用new来创建此类的实例了。
我们用形如new Singleton()来创建类的实例,这也就是调用了类的构造函数。如果不想让类外有权限创建此类的实例,用private修饰构造函数即可。
private Singleton(){}
(2)将获取实例的方法getInstance()用static修饰。
这样,外界想获取类的实例,直接用Singleton.getInstance()方式就可以了。
public static Singleton getInstance(){};
(3)在getInstance()方法中,系统首先判断是否已经存在实例,如果有了实例,则直接返回此实例即可。否则,创建一个实例,返回结果。
public static Singleton getInstance()
{
if(instance==null){
instance=new Singleton();
}
return instance;
}
类图
代码实现
public class Singleton
{
private static Singleton instance;
//将构造方法限定为private,避免类在外部被实例化
private Singleton(){}
public static Singleton getInstance()
{
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
需要说明一点的是,我们这段代码在第一次被引用时,才会将自己实例化。如果系统不需要这个实例,则永远不会产生。这属于“懒汉式”。
在多线程下的实现 |
为什么说经典的单例模式是不安全的?
单例模式在多线程环境下是不安全的,因为可能两个线程A、B同时进入getInstance()方法,A线程判断此时instance不存在,打算去创建。还没创建完,cpu资源被线程B夺去,B判断此时instance不存在,也打算去创建。最终,生成了两个instance。
在多线程下应该怎么做?
有3种措施:
- 使用同步锁(synchronized)
因为synchronized是重量级的,使用会降低性能。所以不推荐使用。
- 使用饿汉式(类加载时就将自己实例化)
- 使用双重检查加锁
为什么要加双重检查,而不是单层?
第一层检查的作用:
线程到来后,先判断是否有实例了,如果有了,则不用往下走了。如果没有,再去执行获取锁等接下来的一系列操作。
第二层检查的作用:
我们可以想一个场景,两个线程A B同时进入到第一层的instance==null,然后A获取到锁,去创建实例。等它出来后,B获取到锁,也要去创建实例。如果此时不对“实例是否存在”进行判断,B也会成功创建一个新实例,那此时就不叫“单例模式”了。