马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
声明:仅为个人学习总结,还请批驳性查察,如有差别观点,接待交换。
择要
《Head First计划模式》第5章条记:团结示例应用和代码,先容单例模式,包罗碰到的题目、接纳的办理方案、以及到达的效果。
1 示例应用
示例应用是巧克力工厂的锅炉控制体系。巧克力锅炉将巧克力和牛奶混淆,加热至沸腾,然后将它们送到制作巧克力棒的下一阶段。
锅炉的状态包罗 boolean empty(是否为空)和 boolean boiled(是否沸腾),相应的状态转换环境如下:
下面是巧克力锅炉的界说,在实验 fill()、boil()、drain() 利用时,都举行了严格的状态判断,防止出现锅炉空烧、排挤未煮沸巧克力等糟糕环境。
- public class ChocolateBoiler {
- private boolean empty;
- private boolean boiled;
- public ChocolateBoiler() {
- empty = true;
- boiled = false;
- }
- public void fill() {
- if (isEmpty()) { // 状态为空时,才可以执行操作
- System.out.println("向锅炉中加满牛奶和巧克力");
- empty = false;
- boiled = false;
- }
- }
- public void drain() {
- if (!isEmpty() && isBoiled()) { // 状态为满并且沸腾时,才可以执行操作
- System.out.println("从锅炉中排出牛奶和巧克力");
- empty = true;
- boiled = false;
- }
- }
- public void boil() {
- if (!isEmpty() && !isBoiled()) { // 状态为满并且未沸腾时,才可以执行操作
- System.out.println("将锅炉中的牛奶和巧克力煮沸");
- boiled = true;
- }
- }
- public boolean isEmpty() { return empty; }
- public boolean isBoiled() { return boiled; }
- }
复制代码 除了状态监控 ,为包管体系正常运行,还要制止为一台锅炉创建多个 ChocolateBoiler 实例。否则,由它们共同利用锅炉,环境也会很糟糕。
2 引入计划模式
接下来,我们实验通过计划模式来确保一个类 ChocolateBoiler 只能创建单一实例(单例)。
显然,通过 new ChocolateBoiler() 可以创建一个实例;但是,再次实验 new ChocolateBoiler() 还会创建另一个实例。
以是,我们的目标是让 new ChocolateBoiler() 只能被实验一次。
要告竣目标,可以分为下面 3 个步调。
2.1 私有构造方法
以 MyClass 类为例。
思索题
- 下面哪个或哪些选项可以将 MyClass 类实例化?【答案在第 20 行】
- public class MyClass {
- // ...
- private MyClass() {}
- }
- A. MyClass 的包外类
- B. MyClass 的同包类
- C. MyClass 的子类
- D. MyClass 的内部代码
- E. 没有任何办法
- 答案:D
- 解析:一个类的 private 成员(包括构造方法)只能从“它所在类的内部”访问。
复制代码 告竣目标的第1步:界说私有构造方法,制止外部类直接实验 new MyClass() 创建实例。
2.2 静态方法
类的静态成员(静态方法和静态变量),属于类本身,而不属于类的某个实例。
通过在类的内部界说 getInstance() 方法,可以访问类的私有构造方法,创建实例。
- public class MyClass {
- // ...
- private MyClass() {}
- public static MyClass getInstance() {
- return new MyClass();
- }
- }
复制代码 通过将 getInstance() 声明为 static 静态方法,可以大概在不创建任何实例的环境下,直接利用类名访问 getInstance()。
- MyClass obj = MyClass.getInstance();
复制代码 告竣目标的第2步:界说静态方法,由类自身实验 new MyClass() 创建实例,并对外提供获取实例的同一接口。
2.3 静态变量
静态变量属于类本身,用于存储“类级别”的状态或共享数据。由于它与类的任何实例都无关,以是可以用来控制实例的创建。
通过在类的内部界说静态变量 uniqueInstance,控制 MyClass 只能创建单一实例(单例)。
- public class MyClass {
- private static MyClass uniqueInstance; // 在类加载时被初始化为 null
- private MyClass() {}
- public static MyClass getInstance() {
- if (uniqueInstance == null) { // 在还没有创建任何实例的情况下,可以创建实例,确保实例的唯一性
- uniqueInstance = new MyClass(); // 使用 uniqueInstance 引用该实例
- }
- return uniqueInstance; // 返回已经创建的实例
- }
- }
复制代码 告竣目标的第3步:界说静态变量,引用类的唯一实例,限定 new MyClass() 只能被实验一次。
2.4 经典单例实现
我们利用一个私有构造方法、一个静态方法、一个静态变量,实现了经典的单例模式。
- public class Singleton {
- // 一个静态变量(私有),引用 Singleton 的唯一实例
- private static Singleton uniqueInstance;
- // 在这里添加其它有用的变量
- // 将构造方法声明为私有的,只有 Singleton 可以实例化这个类
- private Singleton() {
- // ...
- }
- // 一个静态方法(公共),用于创建唯一的 Singleton 实例,并将其返回
- public static Singleton getInstance() {
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- // 在这里添加其它有用的方法
- // 当然,Singleton 是一个正常的类,会有一些实现相应功能的变量和方法
- }
复制代码 在单例模式中:
- 由一个类来管理本身的唯一实例,要想访问实例,只能通过该类;
- 在必要访问实例时,只必要调用该类提供的静态方法(即,该实例的全局访问点)。
2.5 单例模式界说
单例模式(Singleton Pattern)
包管一个类仅有一个实例,并提供一个访问它的全局访问点。
Ensure a class only has one instance, and provide a global point of access to it.
- 私有(或掩护)构造函数 Singleton(),确保外部无法直接实例化;
- 私有(或掩护)静态成员变量 uniqueInstance,引用 Singleton 的唯一实例;
- 公共静态成员方法 getInstance(),提供访问 Singleton 唯一实例的全局接口;
- 业务干系的数据 usefulSingletonData 和方法 usefulSingletonMethod(),用于实现类的详细功能。
单例模式的优点:
- 控制实例访问
通过 getInstance() 方法,可以严格控制实例的访问机遇和访问方式。
- 相比全局变量的上风
- 实例唯一性:单例可以包管实例唯一,而全局变量无法包管;
- 延伸初始化:单例可以根据必要创建实例,而全局变量在步伐启动时就会创建(无论是否会用到);
- 定名空间占用:单例的实例被封装在类内部,而全局变量直接占用全局定名空间(定名空间污染)。
- 实例数目可变
通过修改 2.3 静态变量 的实现,也可以让 Singleton 类管理本身的多个实例。
延伸阅读:《计划模式:可复用面向对象软件的底子》 3.5 Singleton(单件)— 对象创建型模式 [P96-102]
2.6 第1版改进
接纳单例模式,已经实现让一个类只能创建单一实例。下面是改进后的 ChocolateBoiler 类:
- public class ChocolateBoiler {
- private boolean empty;
- private boolean boiled;
-
- // 增加静态变量、静态方法,修改构造方法为私有
- private static ChocolateBoiler uniqueInstance;
- private ChocolateBoiler() {
- empty = true;
- boiled = false;
- }
- public static ChocolateBoiler getInstance() {
- if (uniqueInstance == null) {
- uniqueInstance = new ChocolateBoiler();
- }
- return uniqueInstance;
- }
- // 后面的代码没有变化
- public void fill() {
- if (isEmpty()) { // 状态为空时,才可以执行操作
- System.out.println("向锅炉中加满牛奶和巧克力");
- empty = false;
- boiled = false;
- }
- }
- public void drain() {
- if (!isEmpty() && isBoiled()) { // 状态为满并且沸腾时,才可以执行操作
- System.out.println("从锅炉中排出牛奶和巧克力");
- empty = true;
- boiled = false;
- }
- }
- public void boil() {
- if (!isEmpty() && !isBoiled()) { // 状态为满并且未沸腾时,才可以执行操作
- System.out.println("将锅炉中的牛奶和巧克力煮沸");
- boiled = true;
- }
- }
- public boolean isEmpty() { return empty; }
- public boolean isBoiled() { return boiled; }
- }
复制代码
3 碰到题目
糟糕!在利用多线程对巧克力锅炉控制器举行优化后,锅炉发生了溢出!
我们来查找一下题目的缘故原由。
下面是锅炉控制器 BoilerController 中的干系代码:
- ChocolateBoiler boiler = ChocolateBoiler.getInstance();
- boiler.fill(); // 加满
- boiler.boil(); // 煮沸
- boiler.drain(); // 排出
复制代码 如今有两个线程都必要实验上述代码:
- 假如它们引用雷同的 ChocolateBoiler 实例,就会共享划一的状态,并通过对状态的严格监控
,让锅炉运转精良;(此处忽略数据竞争)
- 假如它们引用差别的 ChocolateBoiler 实例,就会拥有各自的状态,假如一个实例已经加满锅炉,而另一个实例照旧空置状态,并继续参加质料,就会导致锅炉溢出。
但是,ChocolateBoiler 已经界说为单例,这两个线程还会引用差别的实例吗?
我们来细致分析一下 getInstance() 的界说,以及它大概的实验时序。
- public static ChocolateBoiler getInstance() {
- if (uniqueInstance == null) {
- uniqueInstance = new ChocolateBoiler();
- }
- return uniqueInstance;
- }
复制代码 时序线程1线程2uniqueInstance值1ChocolateBoiler.getInstance()null2if (uniqueInstance == null)null3挂起ChocolateBoiler.getInstance()null4if (uniqueInstance == null)null5uniqueInstance = new ChocolateBoiler();object16return uniqueInstance;object17uniqueInstance = new ChocolateBoiler();object28return uniqueInstance;object2在多线程环境下,果然有大概创建两个 ChocolateBoiler 实例 object1 和 object2。
为了确保实例的创建是线程安全的,我们有多种可选方案,包罗同步方法、急迫实例化、双重查抄锁定等。
3.1 多线程方案1:同步方法
只要把 getInstance() 酿成同步方法(添加 synchronized 关键字),就可以包管实例创建的线程安全性。
Java 概念:内涵锁(intrinsic lock)
每个 Java 对象都有一个内涵锁,得到对象的内涵锁就可以大概独占该对象的访问权,试图访问被锁定对象的线程将被壅闭,直到持有该锁的线程开释锁。利用 synchronized 关键字可以得到对象的内涵锁。
Java 概念:方法同步(Method Synchronization)
- 当一个线程调用一个对象的非静态 synchronized 方法时,它会在方法实验之前,自动实验得到该对象的内涵锁;在方法返回之前,线程不停持有锁。
- 一旦某个线程锁定了某个对象,其他线程就不能实验同一个对象的“同一方法或其他同步方法”,只能壅闭等候,直到这个锁再次酿成可用的为止。
- 锁还可以重入(reentrant),这意味着持有锁的线程可以调用同一对象上的其他同步方法;当最外层同步方法返回时,会开释该对象的内涵锁。
- 静态方法也可以同步,在这种环境下,会利用与该方法的类关联的 Class 对象的锁(每个类都有一个对应的 Class 对象,如 Singleton.class,包罗该类的元数据)。
- public class Singleton {
- private static Singleton uniqueInstance;
- private Singleton() {}
- // synchronized 保证没有两个线程可以同时执行 getInstance()
- // 一旦某个线程开始执行 getInstance(),就会获得锁;其它线程再调用 getInstance(),会阻塞等待,直到持有锁的线程释放锁
- public static synchronized Singleton getInstance() {
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- return uniqueInstance;
- }
- public String getDescription() { return "I'm a thread safe Singleton!"; }
- }
复制代码 同步方法实现简朴,但是也有显着的缺点:
- 运行时开销大:同步一个方法大概会使性能降落 100 倍(synchronizing a method can decrease performance by a factor of 100);
- 存在不须要的资源浪费:实际上,只有第一次实验 getInstance() 创建实例时,才必要同步;然而,如今每次实验 getInstance() 都必要同步。
不外,假如 getInstance() 的性能对应用来说并不紧张,那么利用同步方法也没有题目。
3.2 多线程方案2:急迫实例化
在调用 getInstance() 时创建实例,被称为延伸初始化(Lazy Initialization);
与之相对的,可以在类加载时直接创建实例,即急迫初始化(Eager Initialization);由于类加载具有线程安全性,以是实例的创建也是线程安全的。
- public class Singleton {
- // 在类加载时直接创建实例,初始化静态变量
- private static Singleton uniqueInstance = new Singleton();
- private Singleton() {}
- // 直接返回已经创建的实例
- public static Singleton getInstance() {
- return uniqueInstance;
- }
- public String getDescription() { return "I'm a thread safe Singleton!"; }
- }
复制代码 急迫实例化的特点和干系影响如下:
- 特点:肯定会创建实例;
影响:假如应用有大概不利用实例,那么会造成不须要的资源浪费。
- 特点:在应用启动时就会创建实例;
影响:假如实例的创建或运行比力斲丧资源,那么会给应用带来肯定的负担。
假如应用必要尽早的利用实例,大概即便利用的时间比力晚,但实例的创建和运行负担并不重,那么也可以选择急迫实例化。
3.3 多线程方案3:双重查抄锁定
双重查抄锁定(Double-Checked Locking):先查抄实例是否已经创建,假如尚未创建,才举行同步。
以此镌汰 同步方法 中 getInstance() 对同步的利用。(必要 Java 5 及以后版本)
Java 概念:块同步(Block Synchronization)
Java 答应利用 synchronized 关键字来锁定任何对象,从而实今世码块的同步。
synchronized(object) { // 在 object 被锁定的环境下实验某些利用 }
在块中的代码实验之后,锁会被开释。
Java 概念:可见性(Visibility)
在一个单线程步伐中,读取变量的值总是会得到末了写入该变量的值。但是,在 Java 的多线程应用步伐中,一个线程大概看不到另一个线程所做的更改,除非在数据上实验的利用是同步的。然而,同步是有代价的。假如想要的是可见性,而不必要互斥,那么可以利用 volatile 关键字,该关键字可以确保当一个线程修改了变量的值之后,新值对于其他线程立刻可见。
Java 概念:指令重排序(Instruction Reordering)
编译器或处理处罚器为了优化步伐性能,大概会改变指令的实验序次。
比方对于 uniqueInstance = new Singleton(); 语句,其包罗的步调表现如下:
- memory = allocate(sizeof(Singleton.class)); 在堆内存中为对象分配空间
- construct(memory, Singleton.class); 在分配的内存空间上调用构造函数来初始化对象
- uniqueInstance = memory; 将对象引用指向分配的内存空间
在举行指令重排序后,步调3大概会在步调2之前实验。假如另一个线程在“步调3之后、步调2之前”访问 uniqueInstance,就会获取到一个未完全初始化的实例。通过将 uniqueInstance 声明为 volatile,可以克制这种重排序。如许,当 uniqueInstance 不为 null 时,它所引用的实例就是完全初始化的。
- public class Singleton {
- // 将 uniqueInstance 声明为 volatile,确保其可见性,并禁止指令重排序
- private volatile static Singleton uniqueInstance;
- private Singleton() {}
- public static Singleton getInstance() {
- if (uniqueInstance == null) { // 【第一次检查】实例是否创建,只有尚未创建,才进入同步块;
- synchronized (Singleton.class) { // 尝试加锁,如果其它线程已经加锁,则阻塞等待;
- if (uniqueInstance == null) { // 成功加锁后,【再次检查】实例是否尚未创建,
- uniqueInstance = new Singleton(); // 因为在阻塞等待的过程中,其它线程可能已经创建实例。
- }
- }
- }
- return uniqueInstance;
- }
- public String getDescription() { return "I'm a thread safe Singleton!"; }
- }
复制代码 假如利用同步方法存在性能题目,那么利用双重查抄锁定,则可以分身性能和线程安全。
3.4 别的注意事项
除了创建实例时的线程安全题目,在 java 中,利用单例尚有一些别的注意事项:
- 类加载器题目
- 题目形貌:假如有两个或多个类加载器,就可以多次加载同一个类(每个类加载器一次)。假如这个类刚好是一个单例,就会有多于一个的实例。
- 办理办法:确保单例通过同一个类加载器加载,通常利用体系类加载器(即启动类加载器)加载单例。
- 反射题目
- 题目形貌:通过反射可以调用类的私有构造方法,因此大概会创建类的多个实例。
- 办理办法:在构造方法中添加防御性代码,防止通过反射创建多个实例。比方,可以在构造方法中查抄是否已经存在实例,假如存在则抛出非常。
- 序列化和反序列化题目
- 题目形貌:当单例实现了 Serializable 接口时,序列化会将对象的状态生存下来,之后反序列化可以重修对象,如许大概会创建类的多个实例。
- 办理办法:在单例中添加一个 readResolve 方法,该方法在反序列化时会被调用,可以返回单例实例,而不是创建一个新的实例。
3.5 利用摆列
Java 概念:摆列(Enum)
在 Java 中,摆列是一种特别的类,是 java.lang.Enum 的子类,它的特性包罗:
- 摆列类默认具有私有的构造方法,而且不答应显式界说非私有构造方法,因此无法从外部实例化;而且也不能通过反射来访问构造方法;
- 摆列实例在类被加载到 JVM 时静态初始化,包管了实例的唯一性和线程安全性;
- 摆列类在序列化和反序列化的过程中,会由 JVM 包管摆列实例的唯一性;
- 每个摆列常量自动被视为 public static final,而且是摆列范例的一个实例;
- 摆列类也可以界说本身的方法和变量。
由于 Java 会包管摆列类中每个摆列常量的唯一性,以是通过界说一个包罗单个摆列常量的摆列类,就可以自然地实现单例模式。
- public enum Singleton {
- UNIQUE_INSTANCE;
- // 可以添加有用的变量和方法
- public String getDescription() {
- return "I'm a thread safe Singleton!";
- }
- }
复制代码 摆列的利用:
- Singleton singleton = Singleton.UNIQUE_INSTANCE;
- System.out.println(singleton.getDescription());
复制代码 利用摆列实现单例,代码简便明白,而且可以制止前文提到的全部单例题目,包罗创建实例时的线程安全、类加载题目、反射题目、以及序列化和反序列化题目。因此,摆列是实现单例模式的一种保举方式。
4 示例代码
4.1 Java 示例
双重查抄锁定方式:
- // ChocolateBoiler.java
- public class ChocolateBoiler {
- private boolean empty;
- private boolean boiled;
- private volatile static ChocolateBoiler uniqueInstance;
- private ChocolateBoiler() {
- empty = true;
- boiled = false;
- System.out.println("[" + Thread.currentThread().getName() + "] 创建巧克力锅炉实例,初始状态:空、未沸");
- }
- public static ChocolateBoiler getInstance() {
- if (uniqueInstance == null) {
- synchronized (ChocolateBoiler.class) {
- if (uniqueInstance == null) {
- uniqueInstance = new ChocolateBoiler();
- }
- }
- }
- System.out.println("[" + Thread.currentThread().getName() + "] 返回巧克力锅炉实例,当前状态:" + (uniqueInstance.isEmpty() ? "空" : "满") + "、" + (uniqueInstance.isBoiled() ? "沸腾" : "未沸"));
- return uniqueInstance;
- }
- public synchronized void fill() {
- System.out.println("[" + Thread.currentThread().getName() + "] 尝试加满,当前状态:" + (isEmpty() ? "空" : "满") + "、" + (isBoiled() ? "沸腾" : "未沸"));
- if (isEmpty()) {
- System.out.println(" => 向锅炉中加满牛奶和巧克力");
- empty = false;
- boiled = false;
- }
- }
- public synchronized void drain() {
- System.out.println("[" + Thread.currentThread().getName() + "] 尝试排出,当前状态:" + (isEmpty() ? "空" : "满") + "、" + (isBoiled() ? "沸腾" : "未沸"));
- if (!isEmpty() && isBoiled()) {
- System.out.println(" => 从锅炉中排出牛奶和巧克力");
- empty = true;
- boiled = false;
- }
- }
- public synchronized void boil() {
- System.out.println("[" + Thread.currentThread().getName() + "] 尝试煮沸,当前状态:" + (isEmpty() ? "空" : "满") + "、" + (isBoiled() ? "沸腾" : "未沸"));
- if (!isEmpty() && !isBoiled()) {
- System.out.println(" => 将锅炉中的牛奶和巧克力煮沸");
- boiled = true;
- }
- }
- public synchronized boolean isEmpty() { return empty; }
- public synchronized boolean isBoiled() { return boiled; }
- }
复制代码 摆列方式:
- // ChocolateBoilerEnum.java
- public enum ChocolateBoilerEnum {
- UNIQUE_INSTANCE;
- private boolean empty;
- private boolean boiled;
- private ChocolateBoilerEnum() {
- empty = true;
- boiled = false;
- System.out.println("[" + Thread.currentThread().getName() + "] 创建巧克力锅炉实例,初始状态:空、未沸");
- }
- public synchronized void fill() { /* 代码相同 */ }
- public synchronized void drain() { /* 代码相同 */ }
- public synchronized void boil() { /* 代码相同 */ }
- public synchronized boolean isEmpty() { /* 代码相同 */ }
- public synchronized boolean isBoiled() { /* 代码相同 */ }
- }
复制代码 测试代码:
- // BoilerController.java
- public class BoilerController {
- public static void main(String args[]) {
- Runnable boilerTask = new Runnable() {
- @Override
- public void run() {
- ChocolateBoiler boiler = ChocolateBoiler.getInstance();
- // ChocolateBoilerEnum boiler = ChocolateBoilerEnum.UNIQUE_INSTANCE;
- boiler.fill(); // 加满
- boiler.boil(); // 煮沸
- boiler.drain(); // 排出
- }
- };
- Thread thread1 = new Thread(boilerTask);
- Thread thread2 = new Thread(boilerTask);
- thread1.start();
- thread2.start();
- }
- }
复制代码
4.2 C++11 示例
4.2.1 Meyers Singleton
Meyers Singleton 是一种在 C++ 中实现单例模式的简便方法,由 Scott Meyers(Effective C++ 的作者)提出。
- struct Singleton {
- static Singleton& getInstance() {
- static Singleton uniqueInstance;
- return uniqueInstance;
- }
- Singleton(const Singleton&) = delete;
- Singleton& operator=(const Singleton&) = delete;
- private:
- Singleton() = default;
- };
复制代码 这种方法基于 C++11 的包管,即局部静态对象会在“函数第一次实验到该对象的界说处”时被初始化(同时具有线程安全性)。作为额外的利益,假如从未调用“模拟非局部静态对象”的函数(即getInstance()函数,利用在函数内部界说的“局部静态对象”,模拟在全局/定名空间范围内界说的“非局部静态对象”),就永世不会产生构造和析构该对象的开销。
Scott Myers says: “This approach is founded on C++'s guarantee that local static objects are initialized when the object’s definition is first encountered during a call to that function.” … “As a bonus, if you never call a function emulating a non-local static object, you never incur the cost of constructing and destructing the object.”
4.2.2 ChocolateBoiler
局部静态变量方式(Meyers Singleton):
- struct ChocolateBoiler {
- static ChocolateBoiler& getInstance() {
- // 对于局部静态变量,由 C++11 保证只在第一次执行到变量定义处时,进行初始化,并且是线程安全的
- static ChocolateBoiler uniqueInstance;
- std::cout << "[" << std::this_thread::get_id() << "] 返回巧克力锅炉实例,当前状态:" << (uniqueInstance.isEmpty() ? "空" : "满") << "、" << (uniqueInstance.isBoiled() ? "沸腾" : "未沸") << '\n';
- return uniqueInstance;
- }
- void fill() {
- std::lock_guard<std::recursive_mutex> guard(mtx);
- std::cout << "[" << std::this_thread::get_id() << "] 尝试加满,当前状态:" << (isEmpty() ? "空" : "满") << "、" << (isBoiled() ? "沸腾" : "未沸") << '\n';
- if (isEmpty()) {
- std::cout << " => 向锅炉中加满牛奶和巧克力\n";
- empty = false;
- boiled = false;
- }
- }
- void drain() {
- std::lock_guard<std::recursive_mutex> guard(mtx);
- std::cout << "[" << std::this_thread::get_id() << "] 尝试排出,当前状态:" << (isEmpty() ? "空" : "满") << "、" << (isBoiled() ? "沸腾" : "未沸") << '\n';
- if (!isEmpty() && isBoiled()) {
- std::cout << " => 从锅炉中排出牛奶和巧克力\n";
- empty = true;
- boiled = false;
- }
- }
- void boil() {
- std::lock_guard<std::recursive_mutex> guard(mtx);
- std::cout << "[" << std::this_thread::get_id() << "] 尝试煮沸,当前状态:" << (isEmpty() ? "空" : "满") << "、" << (isBoiled() ? "沸腾" : "未沸") << '\n';
- if (!isEmpty() && !isBoiled()) {
- std::cout << " => 将锅炉中的牛奶和巧克力煮沸\n";
- boiled = true;
- }
- }
- bool isEmpty() const {
- std::lock_guard<std::recursive_mutex> guard(mtx);
- return empty;
- }
- bool isBoiled() const {
- std::lock_guard<std::recursive_mutex> guard(mtx);
- return boiled;
- }
- ChocolateBoiler(const ChocolateBoiler&) = delete;
- ChocolateBoiler& operator=(const ChocolateBoiler&) = delete;
- private:
- ChocolateBoiler() : empty(true), boiled(false) {
- std::cout << "[" << std::this_thread::get_id() << "] 创建巧克力锅炉实例,初始状态:空、未沸\n";
- }
- bool empty;
- bool boiled;
- static std::recursive_mutex mtx;
- };
- std::recursive_mutex ChocolateBoiler::mtx;
复制代码 测试代码:
- #include <iostream>
- #include <mutex>
- #include <thread>
- // 在这里添加相关接口和类的定义
- int main() {
- std::function<void()> boilerTask = []() {
- ChocolateBoiler& boiler = ChocolateBoiler::getInstance();
- boiler.fill();
- boiler.boil();
- boiler.drain();
- };
- std::thread t1(boilerTask);
- std::thread t2(boilerTask);
- t1.join();
- t2.join();
- }
复制代码
4.2.3 一次性互斥
与 Java 的双重查抄锁定相比,C++11 提供了更为简便的“一次性互斥”机制。
通过 std: nce_flag 类和 std::call_once() 函数模板来实现一次性互斥,确保纵然有多个线程、多次、同时调用某个函数(可调用对象),其只会被实验一次。复习回首 线程池2-线程互斥 => 3.1.3 一次性互斥。
- struct Singleton {
- static Singleton& getInstance() {
- std::call_once(initFlag, []() { uniqueInstance.reset(new Singleton()); });
- return *uniqueInstance;
- }
- Singleton(const Singleton&) = delete;
- Singleton& operator=(const Singleton&) = delete;
- private:
- Singleton() = default;
- static std::unique_ptr<Singleton> uniqueInstance;
- static std::once_flag initFlag;
- };
- std::unique_ptr<Singleton> Singleton::uniqueInstance;
- std::once_flag Singleton::initFlag;
复制代码
5 计划工具箱
5.1 OO 底子
OO 底子回首
- 抽象(Abstraction)
- 封装(Encapsulation)
- 继续(Inheritance)
- 多态(Polymorphism)
5.2 OO 原则
5.2.1 新原则
本章没有先容新的 OO 原则。
5.2.2 原则回首
- 封装变革。
Encapsulate what varies.
- 针对接口编程,而不是针对实现编程。
Program to interfaces, not implementations.
- 优先利用组合,而不是继续。
Favor composition over inheritance.
- 只管做到交互对象之间的松耦合计划。
Strive for loosely coupled designs between objects that interact.
- 类应该对扩睁开放,对修改关闭。
Classes should be open for extension, but closed for modification.
- 依赖抽象,不依赖详细类。
Depend on abstractions. Do not depend on concrete classes.
5.3 OO 模式
5.3.1 新模式
单例模式(Singleton Pattern)
- 确保一个类只有一个实例,并提供一个全局访问点。
The Singleton Pattern ensures a class has only one instance, and provides a global point of access to it.
5.3.2 模式回首
1 创建型模式(Creational Patterns)
创建型模式与对象的创建有关。
Creational patterns concern the process of object creation.
- 工厂方法(Factory Method)
- 界说了一个创建对象的接口,但由子类决定要实例化哪个类。
The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate.
- 工厂方法让类把实例化推迟到子类。
Factory Method lets a class defer instantiation to subclasses.
- 抽象工厂(Abstract Factory)
- 提供一个接口,创建干系或依赖对象的眷属,而不必要指定详细类。
The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.
2 结构型模式(Structural Patterns)
结构型模式处理处罚类或对象的组合。
Structural patterns deal with the composition of classes or objects.
- 装饰者模式(Decorator Pattern)
- 动态地给一个对象添加一些额外的职责。
The Decorator Pattern attaches additional responsibilities to an object dynamically.
- 就增长功能来说,装饰者模式相比天生子类更为机动。
Decorators provide a flexible alternative to subclassing for extending functionality.
3 举动型模式(Behavioral Patterns)
举动型模式形貌类或对象之间的交互方式以及职责分配方式。
Behavioral patterns characterize the ways in which classes or objects interact and distribute responsibility.
- 计谋模式(Strategy Pattern)
- 界说一个算法眷属,把此中的算法分别封装起来,使得它们之间可以相互更换。
Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.
- 让算法的变革独立于利用算法的客户。
Strategy lets the algorithm vary independently from clients that use it.
- 观察者模式(Observer Pattern)
- 界说对象之间的一对多依赖,
The Observer Pattern defines a one-to-many dependency between objects
- 如许一来,当一个对象改变状态时,它的全部依赖者都会被关照并自动更新。
so that when one object changes state, all of its dependents are notified and updated automatically.
参考
- [美]弗里曼、罗布森著,UMLChina译.Head First计划模式.中国电力出书社.2022.2
- [美]伽玛等著,李英军等译.计划模式:可复用面向对象软件的底子.呆板工业出书社.2019.3
- wickedlysmart: Head First计划模式 Java 源码
- [加]布迪·克尼亚万著,沈泽刚译.Java经典入门指南.人民邮电出书社.2020.6
Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.
|