一般网站用什么数据库西安注册公司官网
- 作者: 五速梦信息网
- 时间: 2026年03月21日 07:02
当前位置: 首页 > news >正文
一般网站用什么数据库,西安注册公司官网,在线做venn图网站,如何防止别人攻击自己的网站声明#xff1a;仅为个人学习总结#xff0c;还请批判性查看#xff0c;如有不同观点#xff0c;欢迎交流。
摘要
《Head First设计模式》第5章笔记#xff1a;结合示例应用和代码#xff0c;介绍单例模式#xff0c;包括遇到的问题、采用的解决方案、以及达到的效果。…声明仅为个人学习总结还请批判性查看如有不同观点欢迎交流。
摘要
《Head First设计模式》第5章笔记结合示例应用和代码介绍单例模式包括遇到的问题、采用的解决方案、以及达到的效果。 目录 摘要1 示例应用2 引入设计模式2.1 私有构造方法2.2 静态方法2.3 静态变量2.4 经典单例实现2.5 单例模式定义2.6 第1版改进 3 遇到问题3.1 多线程方案1同步方法3.2 多线程方案2急切实例化3.3 多线程方案3双重检查锁定3.4 其它注意事项3.5 使用枚举 4 示例代码4.1 Java 示例4.2 C11 示例 5 设计工具箱5.1 OO 基础5.2 OO 原则5.3 OO 模式 参考 1 示例应用
示例应用是巧克力工厂的锅炉控制系统。巧克力锅炉将巧克力和牛奶混合加热至沸腾然后将它们送到制作巧克力棒的下一阶段。
锅炉的状态包括 boolean empty是否为空和 boolean boiled是否沸腾相应的状态转换情况如下 #mermaid-svg-atxrOtTXcxAdsISE {font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-atxrOtTXcxAdsISE .error-icon{fill:#552222;}#mermaid-svg-atxrOtTXcxAdsISE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-atxrOtTXcxAdsISE .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-atxrOtTXcxAdsISE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-atxrOtTXcxAdsISE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-atxrOtTXcxAdsISE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-atxrOtTXcxAdsISE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-atxrOtTXcxAdsISE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-atxrOtTXcxAdsISE .marker.cross{stroke:#333333;}#mermaid-svg-atxrOtTXcxAdsISE svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-atxrOtTXcxAdsISE .label{font-family:“trebuchet ms”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-atxrOtTXcxAdsISE .cluster-label text{fill:#333;}#mermaid-svg-atxrOtTXcxAdsISE .cluster-label span{color:#333;}#mermaid-svg-atxrOtTXcxAdsISE .label text,#mermaid-svg-atxrOtTXcxAdsISE span{fill:#333;color:#333;}#mermaid-svg-atxrOtTXcxAdsISE .node rect,#mermaid-svg-atxrOtTXcxAdsISE .node circle,#mermaid-svg-atxrOtTXcxAdsISE .node ellipse,#mermaid-svg-atxrOtTXcxAdsISE .node polygon,#mermaid-svg-atxrOtTXcxAdsISE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-atxrOtTXcxAdsISE .node .label{text-align:center;}#mermaid-svg-atxrOtTXcxAdsISE .node.clickable{cursor:pointer;}#mermaid-svg-atxrOtTXcxAdsISE .arrowheadPath{fill:#333333;}#mermaid-svg-atxrOtTXcxAdsISE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-atxrOtTXcxAdsISE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-atxrOtTXcxAdsISE .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-atxrOtTXcxAdsISE .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-atxrOtTXcxAdsISE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-atxrOtTXcxAdsISE .cluster text{fill:#333;}#mermaid-svg-atxrOtTXcxAdsISE .cluster span{color:#333;}#mermaid-svg-atxrOtTXcxAdsISE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-atxrOtTXcxAdsISE :root{–mermaid-font-family:“trebuchet ms”,verdana,arial,sans-serif;} 初始 加满fill 煮沸boil 排出drain 结束 始 空 未沸 满 未沸 满 沸腾 终 下面是巧克力锅炉的定义在执行 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; // 在类加载时被初始化为 nullprivate 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. #mermaid-svg-lC2SQL5U5N4v2lhj {font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-lC2SQL5U5N4v2lhj .error-icon{fill:#552222;}#mermaid-svg-lC2SQL5U5N4v2lhj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lC2SQL5U5N4v2lhj .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-lC2SQL5U5N4v2lhj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lC2SQL5U5N4v2lhj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lC2SQL5U5N4v2lhj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lC2SQL5U5N4v2lhj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lC2SQL5U5N4v2lhj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lC2SQL5U5N4v2lhj .marker.cross{stroke:#333333;}#mermaid-svg-lC2SQL5U5N4v2lhj svg{font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lC2SQL5U5N4v2lhj g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:“trebuchet ms”,verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-lC2SQL5U5N4v2lhj g.classGroup text .title{font-weight:bolder;}#mermaid-svg-lC2SQL5U5N4v2lhj .nodeLabel,#mermaid-svg-lC2SQL5U5N4v2lhj .edgeLabel{color:#131300;}#mermaid-svg-lC2SQL5U5N4v2lhj .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-lC2SQL5U5N4v2lhj .label text{fill:#131300;}#mermaid-svg-lC2SQL5U5N4v2lhj .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-lC2SQL5U5N4v2lhj .classTitle{font-weight:bolder;}#mermaid-svg-lC2SQL5U5N4v2lhj .node rect,#mermaid-svg-lC2SQL5U5N4v2lhj .node circle,#mermaid-svg-lC2SQL5U5N4v2lhj .node ellipse,#mermaid-svg-lC2SQL5U5N4v2lhj .node polygon,#mermaid-svg-lC2SQL5U5N4v2lhj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lC2SQL5U5N4v2lhj .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-lC2SQL5U5N4v2lhj g.clickable{cursor:pointer;}#mermaid-svg-lC2SQL5U5N4v2lhj g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-lC2SQL5U5N4v2lhj g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-lC2SQL5U5N4v2lhj .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-lC2SQL5U5N4v2lhj .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-lC2SQL5U5N4v2lhj .dashed-line{stroke-dasharray:3;}#mermaid-svg-lC2SQL5U5N4v2lhj #compositionStart,#mermaid-svg-lC2SQL5U5N4v2lhj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #compositionEnd,#mermaid-svg-lC2SQL5U5N4v2lhj .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #dependencyStart,#mermaid-svg-lC2SQL5U5N4v2lhj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #dependencyStart,#mermaid-svg-lC2SQL5U5N4v2lhj .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #extensionStart,#mermaid-svg-lC2SQL5U5N4v2lhj .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #extensionEnd,#mermaid-svg-lC2SQL5U5N4v2lhj .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #aggregationStart,#mermaid-svg-lC2SQL5U5N4v2lhj .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj #aggregationEnd,#mermaid-svg-lC2SQL5U5N4v2lhj .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lC2SQL5U5N4v2lhj .edgeTerminals{font-size:11px;}#mermaid-svg-lC2SQL5U5N4v2lhj :root{–mermaid-font-family:“trebuchet ms”,verdana,arial,sans-serif;} Singleton -Singleton uniqueInstance -usefulSingletonData -Singleton() getInstance() : Singleton usefulSingletonMethod() 私有或保护构造函数 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 Im 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 Im 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 Im 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 Im 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() {Overridepublic 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 C11 示例 4.2.1 Meyers Singleton
Meyers Singleton 是一种在 C 中实现单例模式的简洁方法由 Scott MeyersEffective C 的作者提出。
struct Singleton {static Singleton getInstance() {static Singleton uniqueInstance; return uniqueInstance;}Singleton(const Singleton) delete;Singleton operator(const Singleton) delete;private:Singleton() default;
};这种方法基于 C11 的保证即局部静态对象会在“函数第一次执行到该对象的定义处”时被初始化同时具有线程安全性。作为额外的好处如果从未调用“模拟非局部静态对象”的函数即getInstance()函数使用在函数内部定义的“局部静态对象”模拟在全局/命名空间范围内定义的“非局部静态对象”就永远不会产生构造和析构该对象的开销。 Scott Myers says: “This approach is founded on Cs 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() {// 对于局部静态变量由 C11 保证只在第一次执行到变量定义处时进行初始化并且是线程安全的static ChocolateBoiler uniqueInstance;std::cout [ std::this_thread::get_id() ] 返回巧克力锅炉实例当前状态 (uniqueInstance.isEmpty() ? 空 : 满) 、 (uniqueInstance.isBoiled() ? 沸腾 : 未沸) \n;return uniqueInstance;}void fill() {std::lock_guardstd::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_guardstd::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_guardstd::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_guardstd::recursive_mutex guard(mtx);return empty;}bool isBoiled() const {std::lock_guardstd::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::functionvoid() 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 的双重检查锁定相比C11 提供了更为简洁的“一次性互斥”机制。 通过 std::once_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_ptrSingleton uniqueInstance;static std::once_flag initFlag;
};std::unique_ptrSingleton 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.3wickedlysmart: Head First设计模式 Java 源码[加]布迪·克尼亚万著,沈泽刚译.Java经典入门指南.人民邮电出版社.2020.6 Hi, I’m the ENDing, nice to meet you here! Hope this article has been helpful.
- 上一篇: 一般网站宽度移动app与网站建设的区别
- 下一篇: 一般网站自己可以做播放器吗优秀网页模板
相关文章
-
一般网站宽度移动app与网站建设的区别
一般网站宽度移动app与网站建设的区别
- 技术栈
- 2026年03月21日
-
一般网站宽度网站建设结算系统
一般网站宽度网站建设结算系统
- 技术栈
- 2026年03月21日
-
一般网站建设公司怎么收费莱芜招聘
一般网站建设公司怎么收费莱芜招聘
- 技术栈
- 2026年03月21日
-
一般网站自己可以做播放器吗优秀网页模板
一般网站自己可以做播放器吗优秀网页模板
- 技术栈
- 2026年03月21日
-
一般制作一个网站要多久个人网页图片模块制作
一般制作一个网站要多久个人网页图片模块制作
- 技术栈
- 2026年03月21日
-
一般自己怎么做网站做英文网站要会什么
一般自己怎么做网站做英文网站要会什么
- 技术栈
- 2026年03月21日
