设计模式设计模式并非只有 23 种 23 这个数字是有三个大佬(GOF),写了一本设计模式的书,这本书中谈到了 23 种设计模式 设计模式也适合变成语言相关的,有些设计模式,是在给语法填坑 有的语言本身语法设计的更好,不太依赖设计模式 这 23 种设计模式是针对 C# 这个语言来谈的,比较适用于 C++,Java、C# 校招中,掌握 4~5 个就好了 设计模式适合具有一定的编程经验之后再学习,因为缺少经验,难以理解人家为什么这样写 编程这个圈子中,“灵活”是贬义词(易出 bug),“死板”才是褒义词(稳健) 设计模式就是针对编写代码过程中的“软性约束”(不是强制的,选择性遵守,但遵守当然有好处)
框架针对一些特定的场景问题,大佬们把基本的代码已经写好了,大部分逻辑也都写好了,留出来一些空位,让你在空位上填充一些自定制的逻辑 框架就是针对编写代码过程中的“硬性约束” Java 圈子中,特别讲究框架
JDBC
中
- JDBC => DataSourse
数据源描述数据库在哪
- 一般来说,一个程序中,只有一个数据库,对应的mysql
服务器只有一份,此时 DataSourse
这个类没有必要创建出多个类
- 此时就可以使用单例模式,描述DataSourse
,避免不小心创建出了多个实例
- 比如广告系统
- 广告数据都是要加载在内存中的,这里的查询就是直接查内存 hash 表,避免直接插数据库(查数据库太慢)
- 所以就在代码中专门搞了个类,管理这些数据,此时这个类也就是案例模式
- 这个类,一个实例就是几百G 内存空间前提是“一个进程中”,如果有多个进程,自然每个进程中都可以有一个实例
在你们家,每次吃完饭后,如果是你的妈妈洗碗,那一般都是吃完后立即就把碗洗了
但如果是你洗碗,就会把碗先泡一会再洗,但这样容易忘记,一拖,就拖到了下顿使用碗的时候才会洗
你妈妈这种洗碗习惯就相当于是“饿汉模式”
你这种洗碗模式就相当于是“懒汉模式”
“饿”的意思是“迫切”,在类被加载的时候,就会创建出这个单例的实例
Singleton
类中,成员变量 instance
要用 static
修饰,这样 instance
就变成了“类成员”
类成员初始化,就是在 Singleton
这个类被加载的时候(近似于程序启动的时候)class Singleton {
//static修饰,instance 成员变量就是“类成员”
private static Singleton instance = new Singleton();
}getInstance
方法就好了new
这个类,每次需要使用都通过 getInstance
方法来调用到这个实例,此时这个类就是单例的了new
这个类 ”这个才是单例模式中主要需要解决的问题,防止别人不小心 new 了对象类的使用者的想法都很简单,“用就对了”,但类的设计者需要考虑的事就多了
new
了一下单例模式只能避免别人的“失误
”,但无法应对别人的“故意攻击
”:
计算机中,“懒”是褒义词。谈到“懒”,效率会更高,是高效的代名词
在这里,推迟了创建实例的时机,实例会在第一次使用的时候才会创建
经常有这种情况:
中午的时候,需要使用 4 个碗
晚上的时候,只需要使用 2 个碗,此时只需要洗两个碗就可以了
比如,有一个编辑器,打开一个非常大的文本文档,有两种方式:
class SingletonLazy {
//先把实例的引用设为 null,不着急创建实例
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if(instance == null) {
instance = new SingletonLazy();
}
return instance;
}
private SingletonLazy() {}
}
null
,不着急创建实例getInstance
,由于此时引用为 null
,就会进入 if
分支,从而创建实例getInstance
,结果都不会创建实例,直接返回private
,避免被不小心 new
上述写的“饿汉“和“懒汉“单例模式代码,是否是线程安全的?(如果是多线程环境下,调用 getInstance,是否会有问题呢?)
return instance
只是一个“读操作”,没有涉及到“多个线程对变量进行修改”对于 判定+赋值
操作,产生的线程安全问题:
if(instance == null) {
instance = new SingletonLazy();
}
调度顺序 | 线程 | t1 | t2 |
---|---|---|---|
| |||
| |||
| |||
|
instance
的值,使得第一次创建的实例没有引用指向,很快就会被垃圾回收机制给消除掉bug
的
- 因为实际上,构造方法内部可能会执行很多的逻辑“先判定,再修改”,这种代码模式,是属于典型的线程不安全代码,因为判定和修改之间可能涉及到线程的切换
通过加锁,来解决问题
if
判定和 new
操作之间出现了线程切换,出现了逻辑上的穿插public static SingletonLazy getInstance() {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazy();
}
}
return instance;
}if
和 new
打包成一个整体就可以了if
判定和 new
操作就是一个“原子”了return
加不加到锁里面无所谓
- 此处的矛盾是 if
和 new
中间出现线程切换,引起逻辑错误,而后面的 return
不会受到影响
- 无论 return
是在本线程还是其他线程,此处的 return
的值都是 instance
内存中的最新值加锁之后,确实解决了线程安全问题,到那时加锁却可能会带来阻塞
如果上述代码,已经 new
完了对象,if
分支再也进不去了,后续的代码,都是单纯的“读操作”,此时 getInstance
不加锁也是线程安全的
这样就没有必要加锁了
而当前的代码写法,虽然没有线程安全问题了(instance
new
出来之后,就都是读操作了),但只要调用了 getInstance
,就都会触发加锁操作,此时就会因为加锁,而产生阻塞,啥时候能恢复执行,中间可能是“沧海桑田”,影响性能
因此,针对这个问题,还需要进行进一步改进
instance == null
),一旦 instance
已经创建好了,不为 null
,意味着此时不需要加锁了,没有线程安全问题了if
,性能会下降很多指令重排序,也是一种编译器的优化方式,在不改变原有代码逻辑的条件下,有的时候调整逻辑执行顺序也能提高性能
instance = new SingletonLazy();
这个语句,可以理解是分成了三个步骤:(粗略)
SingletonLazy
对象分配内存空间(买房)调度顺序 | 线程 | t1 | t2 |
---|---|---|---|
| |||
| |||
| |||
| |||
|
在
t2
中,由于instance
对象内存未初始化,一旦调用的方法里使用了任何instance
的成员,都可能是错误的值,可能会引起一系列不可预期的情况
要解决因为指令重排序导致的线程安全,只需要请出 volatile
关键字就可以了
private static volatile SingletonLazy instance = null;
instance
是易失的(易改变的),之后围绕这个变量的优化,就会非常的克制volatile
之后,也能禁止对 instance
赋值的操作插入到其他操作之间,上述 123 的操作不会再变成 132volatile
有两个功能:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。