1.设计模式的说明
1.1 理解
设计模式是在大量的实践中总结和理论化之后优的代码结构、编程风格、以及解决问题的思考方式。
1.2 常用设计模式 — 23种经典的设计模式 GOF
创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
2.单例模式
2.1 要解决的问题:
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
2.2 具体代码的实现:
饿汉式1:
class Bank{
//1.私化类的构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的,随着类的加载而创建
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
饿汉式2:使用了静态代码块
class Order{
//1.私化类的构造器
private Order(){
}
//2.声明当前类对象,没初始化
//4.此对象也必须声明为static的
private static Order instance = null;
static{
instance = new Order();
}
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
return instance;
}
}
懒汉式:
class Order{
//1.私化类的构造器
private Order(){
}
//2.声明当前类对象,没初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){//懒汉式暂时还存在线程安全问题, 多线程情况下,
多个线程同时执行到 if(instance==null)语句,创建多个引用对象。
instance = new Order();
}
return instance;
}
}
2.3 两种方式的对比:
-
饿汉式:
-
坏处:
- 饿汉式是一种常见的单例模式实现方式,它的主要特点是在类加载时就创建并初始化单例对象,保证了在多线程环境下的线程安全性。虽然饿汉式有一些优点,但也存在一些潜在的坏处:
- 提前占用内存空间:饿汉式在类加载时就创建单例对象,无论后续是否会真正使用该对象,都会占用一定的内存空间。如果单例对象较大或初始化耗时较长,可能会导致程序启动变慢,浪费内存资源。
- 无法延迟加载:饿汉式无法实现延迟加载,即在需要使用单例对象时再进行创建。因为在类加载时就创建了单例对象,无论后续是否真正需要使用,对象都已经存在。
- 可能造成资源浪费:如果在程序运行过程中始终没有使用该单例对象,而它又是在类加载时就创建的,就会造成资源的浪费。特别是在单例对象比较大或初始化代价较高的情况下,这种资源浪费可能会显著影响性能和资源利用率。
- 无法处理异常情况:在饿汉式中,如果在单例对象的初始化过程中发生异常,可能会导致整个应用程序启动失败。这是因为单例对象的创建发生在类加载阶段,而异常无法捕获和处理。
-
好处:饿汉式是线程安全的
-
懒汉式:好处:延迟对象的创建。
-
目前的写法坏处:线程不安全。
-
懒汉式单例模式在多线程环境下存在线程安全问题,主要原因是多个线程可以同时进入到 if (instance == null) 的判断条件中,从而导致创建多个实例对象。
考虑以下情况:
-
当线程 A 和线程 B 同时执行到 if (instance == null) 时,它们都发现 instance 为 null,满足条件。
-
线程 A 进入 if 语句块,开始创建 Order 对象,并执行 instance = new Order();。
-
在线程 A 执行完毕之前,线程 B 也进入 if 语句块,开始创建另一个 Order 对象,并执行 instance = new Order();。
-
最终,就会创建两个不同的 Order 对象,违背了单例模式的原则。
-
这种情况下,多线程并发访问会导致多个实例对象的创建,破坏了单例模式的唯一性。
使用同步机制将单例模式中的懒汉式改写为线程安全的。
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
//
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if(instance == null){ //后面来的线程 如果已经创建好了对象 就可以不进入同步代码块 直接return给一个单例对象。
synchronized (Bank.class) {
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
在 getInstance() 方法上添加 synchronized 关键字,将该方法变为同步方法
,确保在同一时间只有一个线程可以进入该方法,从而避免并发创建多个实例对象。
但这会影响性能,因为每次调用 getInstance() 都需要进行同步。
使用双重检查锁定(double-checked locking)机制,在 getInstance() 方法内部进行双重判断和同步处理。
在第一次检查时,判断 instance 是否为 null,如果是才进入同步块进行实例对象的创建。
这样可以避免每次调用 getInstance() 都进行同步,提高性能。
}
面试题:写一个线程安全的单例模式。
饿汉式。
懒汉式:上面提供的。