单例模式
单例模式
Bummon单例模式
一、定义及注意事项
摘自百度百科:单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
也就是说,一个类负责创建自己的对象,同时确保只有单个对象
被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
在我们使用单例模式的时候需要注意的是:
- 单例类
有且只能有一个实例
- 单例类必须自己创建
自己的唯一实例
。 - 单例类必须给
所有其他对象
提供这一实例。
二、实现方式
UML图
在Java中,一般有两种实现方式:懒汉式
和饿汉式
,顾名思义:
- 懒汉式:比较懒,什么时候使用,什么时候才进行实例化
- 饿汉式:特别饥饿,因为饿怕了所以在创建对象的时候就进行实例化
1. 懒汉式(线程不安全)
1 | public static class Lazy { |
这是懒汉式最基础的一种实现方式,方法在第一次访问的时候才进行实例化,从而达到懒加载的效果。但是这种写法会有线程安全的问题,假如在对象还未实例化的时候被两个线程同时访问,它就有可能出现被实例化多次的情况。那么有没有更好的解决方案呢,自然是有的,给上面的代码加锁就可以了,也就是线程安全的懒汉式。
2. 懒汉式(线程安全)
线程安全的懒汉式有两种写法:一种是把锁加在方法上,另一种是加在代码块中。
(1) 方法加锁
1 | public static class Lazy { |
以上写法便是给懒汉式加了锁后的代码,但是还会存在另一个问题:synchronized修饰的是整个方法,也就是每次访问时都是同步的,但是这个方法只会实例化一次,这样就会导致效率变低。那么大家思考一下,有没有更适合的解决方案呢?其实我们只需要把对方法加锁换成给代码块加锁就可以了。
(2) 代码块加锁
1 | public static class Lazy { |
上面的代码是将锁加在代码块中,那么这种写法是最优解吗?当然在单线程中是没有任何问题的,但是如果是多线程,假如有 线程A 和 线程B 一起访问了该方法,线程A 首先进入 if 拿到锁,此时线程B进入 if 后被同步,等待线程A释放锁,那么当线程A释放后,线程B进入锁又进行了一次实例化。也就是说这种写法也可能会导致多次实例化,此时我们可以看一下双检锁式。
3. 双检锁式
1 | public static class Lazy { |
我们上面在代码中利用 if 进行了两次的判空,大家来思考一下我们为什么要进行两次判空呢?
双检锁式为什么要判空两次
我们可以仔细看一下代码中的两个 if 都有什么作用:
- 首先是第一个 if :如果我们去掉第一个 if,
那么每个进入方法的线程都会被同步
,那就和把锁加在方法上一样了,也就是说会影响效率。 - 第二个 if:我们已经判断过一次非空,如果我们将第二个 if 去掉的话,
在多线程的情况下可能会导致多次实例化的情况
,所以我们要在锁中再判空一次。
4. 饿汉式
1 | public static class Hungry { |
饿汉式也是比较常见的写法,在类加载的时候就已经完成了实例化,效率比较高。当然缺点也很明显,如果该实例没有被用到,那么给其分配的内存就浪费掉了。
三、应用场景
以下部分内容摘自菜鸟教程
意图: 保证一个类仅有一个实例
主要解决: 一个全局使用的类频繁的创建与销毁的场景
何时使用: 当要控制实例数目,节省系统资源时
如何解决: 判断系统是否已经有这个单例,如果有则直接返回,如果没有则创建
关键代码: 构造函数是私有的
应用实例:
- 1、一个班级只有一个班主任。
- 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
四、小结
单例模式是创建型模式
之一,它只允许创建一个对象,节省了多次创建对象的内存和时间;经常被公共访问的对象适用单例模式
。但它不适用于有变化的对象
,如果一个对象在不同场景下会发生变化,单例模式会引起变化,不能适用所有的应用场景。