创立型:单例设计模式 1
目录介绍
- 01. 单例模式介绍
- 02. 单例模式定义
- 03. 单例应用场景
- 04. 思考几个问题
- 05. 为什么要应用单例
- 06. 解决资源拜访抵触
- 07. 示意全局惟一类
01. 单例模式介绍
-
单例模式是利用最广的模式
- 也是最先晓得的一种设计模式,在深刻理解单例模式之前,每当遇到如:getInstance()这样的创立实例的代码时,我都会把它当做一种单例模式的实现。
-
单例模式特点
- 构造函数不对外开放,个别为 private
- 通过一个静态方法或者枚举返回单例类对象
- 确保单例类的对象有且只有一个,尤其是在多线程的环境下
- 确保单例类对象在反序列化时不会从新结构对象
02. 单例模式定义
- 保障一个类仅有一个实例,并提供一个拜访它的全局拜访点
03. 单例应用场景
- 利用中某个实例对象须要频繁的被拜访。
- 利用中每次启动只会存在一个实例。如账号零碎,数据库系统。
04. 思考几个问题
-
网上有很多解说单例模式的文章,但大部分都偏重解说,如何来实现一个线程平安的单例。重点还是心愿搞清楚上面这样几个问题。
- 为什么要应用单例?
- 单例存在哪些问题?
- 单例与动态类的区别?
- 有何代替的解决方案?
05. 为什么要应用单例
-
单例设计模式(Singleton Design Pattern)了解起来非常简单。
- 一个类只容许创立一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
-
重点看一下,为什么咱们须要单例这种设计模式?它能解决哪些问题?接下来我通过两个实战案例来解说。
- 第一个是解决资源拜访抵触;
- 第二个是示意全局惟一类;
06. 解决资源拜访抵触
-
实战案例一:解决资源拜访抵触
-
先来看第一个例子。在这个例子中,咱们自定义实现了一个往文件中打印日志的 Logger 类。具体的代码实现如下所示:
public class Logger { private FileWriter writer; public Logger() {File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true 示意追加写入 } public void log(String message) {writer.write(mesasge); } } // Logger 类的利用示例:public class UserController {private Logger logger = new Logger(); public void login(String username, String password) { // ... 省略业务逻辑代码... logger.log(username + "logined!"); } } public class OrderController {private Logger logger = new Logger(); public void create(OrderVo order) { // ... 省略业务逻辑代码... logger.log("Created an order:" + order.toString()); } }
-
- 看完代码之后,先别着急看我上面的解说,你能够先思考一下,这段代码存在什么问题。
- 在下面的代码中,咱们留神到,所有的日志都写入到同一个文件 /Users/wangzheng/log.txt 中。在 UserController 和 OrderController 中,咱们别离创立两个 Logger 对象。在 Web 容器的 Servlet 多线程环境下,如果两个 Servlet 线程同时别离执行 login() 和 create() 两个函数,并且同时写日志到 log.txt 文件中,那就有可能存在日志信息相互笼罩的状况。
- 为什么会呈现相互笼罩呢?咱们能够这么类比着了解。在多线程环境下,如果两个线程同时给同一个共享变量加 1,因为共享变量是竞争资源,所以,共享变量最初的后果有可能并不是加了 2,而是只加了 1。同理,这里的 log.txt 文件也是竞争资源,两个线程同时往里面写数据,就有可能存在相互笼罩的状况。
-
那如何来解决这个问题呢?咱们最先想到的就是通过加锁的形式:给 log() 函数加互斥锁(Java 中能够通过 synchronized 的关键字),同一时刻只容许一个线程调用执行 log() 函数。具体的代码实现如下所示:
public class Logger { private FileWriter writer; public Logger() {File file = new File("/Users/wangzheng/log.txt"); writer = new FileWriter(file, true); //true 示意追加写入 } public void log(String message) {synchronized(this) {writer.write(mesasge); } } }
-
不过,你认真想想,这真的能解决多线程写入日志时相互笼罩的问题吗?
- 答案是否定的。这是因为,这种锁是一个对象级别的锁,一个对象在不同的线程下同时调用 log() 函数,会被强制要求程序执行。然而,不同的对象之间并不共享同一把锁。在不同的线程下,通过不同的对象调用执行 log() 函数,锁并不会起作用,依然有可能存在写入日志相互笼罩的问题。
07. 示意全局惟一类
- 从业务概念上,如果有些数据在零碎中只应保留一份,那就比拟适宜设计为单例类。
- 比方,配置信息类。在零碎中,咱们只有一个配置文件,当配置文件被加载到内存之后,以对象的模式存在,也理所应当只有一份。
-
再比方,惟一递增 ID 号码生成器,如果程序中有两个对象,那就会存在生成反复 ID 的状况,所以,咱们应该将 ID 生成器类设计为单例。
import java.util.concurrent.atomic.AtomicLong; public class IdGenerator { // AtomicLong 是一个 Java 并发库中提供的一个原子变量类型, // 它将一些线程不平安须要加锁的复合操作封装为了线程平安的原子操作,// 比方上面会用到的 incrementAndGet(). private AtomicLong id = new AtomicLong(0); private static final IdGenerator instance = new IdGenerator(); private IdGenerator() {} public static IdGenerator getInstance() {return instance;} public long getId() {return id.incrementAndGet(); } } // IdGenerator 应用举例 long id = IdGenerator.getInstance().getId();
- 实际上,明天讲到的两个代码实例(Logger、IdGenerator),设计的都并不优雅,还存在一些问题。
更多内容
- GitHub:https://github.com/yangchong211
- 博客:https://juejin.cn/user/197877…
- 博客汇总:https://github.com/yangchong2…