同步和异步
有人说过这么一个例子:
你去饭馆点菜,点完之后哪也不能去,啥也不能做,直到等菜好了,你才能去做其他事情;这就叫同步。
如果你点了菜,你不用等待,你可以回到座位,刷手机,或者做其他事情;这就叫异步。
不必深入其中,跳出来看看
多个事件,当且仅当只能一个活跃,其余的事件都得伴随(等待),这就是同步。
多个事件,相互之间可以通信,但是不干扰对方状态,这就叫异步。
换句话说,同步是多事件的统一外观表现,而异步更关注个体表现。
好似钟表,只有当秒针转了一圈,分针才能动,分针转了一圈,时针才能动,这就是同步。
在完整事件的表现上,同一时间只能有一个活跃分子。
不过,考虑一下最后一步的情况,当五十九秒的时候,下一秒是分秒齐动。
五十九分五十九秒,三针齐动。这个时刻,同时活跃的是多个子事件,也就是异步了。
有个笑话
王大爷为什么能够一遍刷牙一遍唱歌?
正常情况下,刷牙和唱歌的确是一个同步事件,不能够同时发生。不过
因为王大爷刷的是假牙
我们的观念中,因为两个事件会涉及到资源的独占,也就是嘴巴,两者不能够同时发生。
这样拆离以后,两个事件,就能够同时发生了,也就是异步。
这样的例子还可以举出很多
这样的例子还可以举出很多
两个学生考试,其中一个是学渣,一个是学霸
同步:学渣等学霸写完以后,才能开始"动手"
异步:谁也不看谁的,自己做自己的
public class Student {
public String answer;
public void write(String answer){
this.answer = answer;
}
public static void main(String[] args) {
final Student 学渣 = new Student();
final Student 学霸 = new Student();
/**
* 学渣抄学霸
*/
学霸.write("小菜一碟");
学渣.write(学霸.answer);
/**
* 每个人自己做
*/
new Thread(){
@Override
public void run() {
学霸.write("小菜一碟");
}
}.start();
new Thread(){
@Override
public void run() {
学渣.write("我不会啊");
}
}.start();
}
}
并发和并行
还是回到王大爷身上,说说并发和并行。
如果王大爷有好牙口,能不能做到一边刷牙一遍唱歌呢?
可以差不多
做到:刷一两下,唱一两句
好像有点蠢,不过那是因为我们能够看明白
。
电脑只有一个CPU
,我们也经常是多开的运行几个程序,看起来就是同时运行多个程序。
刷一两下,唱一两句,当两件事的切换过程,超过了我们的感知,那看起来就是一起发生的。
- 并发:多个事情相互切换,交替执行
- 并行:同时发生多个事情
形象的理解一下
同时跑完十个赛道
并发:一个人,来回切换赛道跑步
并行:十个人,每人一个赛道
在这里,隐含了一个关键的问题:资源
,或者叫做执行者。
举个例子,结合前面的同步异步,一起理解一下
做西红柿鸡蛋汤有如下步骤
- 切西红柿
- 调匀鸡蛋
- 做成汤
做汤之前,材料一定要准备好,这是必须的,但是
摘菜
和打蛋
之间,存在必然联系么?
材料准备好之前,哪怕油锅烧好了,也得等着,这就是同步。
但是多种材料之间,没有先后,准备好就行,可以同时处理,这就是异步。
一个人,边打蛋,边洗菜,那就是并发了。
两个人,一人打蛋,一人洗菜,这就是并行
分层分类的进行描述
- 同(异)步:简略的事件关系表达
- 并(行)发:事件执行区别的描述
关于事件执行,可以参考着三个方面
- 顺序
- 执行
- 资源
同步和异步,是顺序的再整理。
并发和并行,是执行的细划分。
阻塞和非阻塞:是资源相关性的描述。
阻塞和非阻塞
前面说阻塞和非阻塞,和资源相关,那结合一下生活中的场景
- 上厕所
这个事情的话,男生会比较方便一下,因为有小便池,大号和小号可以隔离开来。
我们来到女厕所,只有一个坑位的女厕所,这时候会发生什么事情?
肯定是堵塞,排队。这就是阻塞。
如果有多个坑位,或者男生有小便池一样,就不会存在这个问题。
- 挤公交
只来了一辆公交,还挤满了,怎么办?
在这里,需要点明一下容易被忽略的点:存在多个执行者
讨论并发和并行的时候,总说多份资源,这是因为要申明的是多个执行者。
在这里,具体描述的就是资源争抢了。
- 阻塞:资源问题导致事件执行不下去
- 非阻塞:不存在资源争抢问题
说起来,关于资源重要的就是两点
- 如何解决公共资源的使用问题
- 如何保证资源的完整性
对资源问题进行归纳,无非三种
- 不释放(死锁)
鲁班造了两个绝世的宝盒,除了钥匙,谁也打不开,而且钥匙只有一把。
鲁班分别把另一个盒子的钥匙锁在了另一个盒子里面,然后,死了。
就这样,独占资源以后,不释放,导致后续事件无限等待,无法执行,这就是死锁。
- 抢不过(饥饿)
挤地铁是个体力活,我面临过两个问题
- 上不去
- 下不来
人潮实在汹涌。
资源也是一样,优先级高的事件,更为强壮,如果全部是强壮的人,瘦弱的我如何能够上(下)车呢。
资源,对我来说,永远也得不到了。
- 太礼貌(活锁)
皇上生病了,召见和珅和纪晓岚试药
和珅:纪大人请
纪晓岚:和大人先请
。。。。。
谁都能下手,谁也下不去手;谁都想要,却谁也不能要。
解决策略
饥饿-无饥饿
人人得排队,警察维护治安,不分男女老幼,一律平等对待。
和优先级无关,非公平锁换成公平锁
死锁-无锁
谁都能使用公共资源,不强制隔离,但是需要一定手段保证资源完整。
常用的CAS
就是这样去实现锁的功能的。
为了不无限等待,可以设置一下等待次数,或者锁超时时间。
无锁之后也不必礼让,造成活锁了。
两个定律
Amdahl
T 1 T_1 T1:优化前系统耗时
T n T_n Tn: n n n个处理器优化后耗时
F F F:串行逻辑占比
T n = T 1 F + T 1 ( 1 − F ) n = T 1 ( F + 1 n ( 1 − F ) ) ⇒ T 1 T n = 1 F + 1 n ( 1 − F ) ⇒ lim n → ∞ T 1 T n = 1 F T_n = T_1F + \frac{T_1(1-F)}{n} \\ = T_1(F + \frac{1}{n}(1 - F)) \\ \\ \Rightarrow \frac{T_1}{T_n} = \frac{1}{F + \frac{1}{n}(1 - F)} \\ \Rightarrow \lim\limits_{n \rightarrow \infty}\frac{T_1}{T_n} = \frac{1}{F} Tn=T1F+nT1(1−F)=T1(F+n1(1−F))⇒TnT1=F+n1(1−F)1⇒n→∞limTnT1=F1
也就是说,如果无法降低串行逻辑的占比,再多的处理器也是无用。
Gustafson
a a a:串行执行时间
b b b:并行执行时间
T 1 = a + n b T_1 = a +nb T1=a+nb
T n = a + b T_n = a + b Tn=a+b
T 1 T n = a + n b a + b = a a + b + n b a + b = F + n ⋅ a + b + a a + b = F + n ( 1 − F ) = n − F ( n − 1 ) ⇒ lim n → ∞ T 1 T n = n − F ( n − 1 ) = 0 \frac{T_1}{T_n} = \frac{a + nb}{ a + b} \\ = \frac{a}{a+b} + \frac{nb}{a+b} \\ = F + n \cdot \frac{a + b + a}{a+ b} \\ = F + n( 1 - F) \\ = n - F(n - 1) \\ \Rightarrow \lim \limits_{n \rightarrow \infty}\frac{T_1}{T_n} = n - F(n - 1) = 0 TnT1=a+ba+nb=a+ba+a+bnb=F+n⋅a+ba+b+a=F+n(1−F)=n−F(n−1)⇒n→∞limTnT1=n−F(n−1)=0
提高n
就能无限提高执行速度?
其中的疏漏点在于 T 1 = a + n b T_1 = a + nb T1=a+nb,当 n n n增大后,默认的降低了串行逻辑占比。
如果全是并行逻辑,理论上的确是这样,但是软件的体量不是无限的,而且串行必定存在。
综合来说,并行一定是会提升执行效率的。
JVM性质
为了提高效率,JVM
肯定是会做一些优化的,就像前面的西红柿鸡蛋汤。
它肯定是并发(行)的去进行打蛋和摘菜,编译过程来看,也就是重排序了。
一般来说,它必须保证如下性质
原子性
原子性说的就是要保证操作的完整性,哪怕只是一部分。
王大爷现在不唱歌了,他想边刷牙边切西瓜。
刷牙时,他要换牙刷。
切瓜时,他要换刀。
如果不能保证原子性,不是西瓜上涂了牙膏,就是把西瓜刀捅到了嘴里。
就是这样,不能保证原子性,没有把牙刷换过来,就要出问题。
可见性
使用的同一份资源,如果对资源的修改不透明,必定会造成误会。
说好一起喝敌敌畏殉情,结果,你拿的却是恒大冰泉,怎么办。
还是王大爷切西瓜,保证了原子性的,的确换了牙刷,但是眼花拿成了刀,认错了资源,哎。
有序性
西红柿鸡蛋汤,你锅烧好了,不等材料准备好,难不成全部放进去?
做个叫花鸡,火好了,鸡都不杀,你这就只是活埋。
可以优化,但是有些事件的强相关顺序还是得保证的。
重排序的话,其他规则如下
- 保证语义完整
- volatile:先写后读
- 锁:先上锁,后解锁
- 传递:先A后B,先B后C,必定A先于C
- 线程:start必定先于其他,操作先于终结,中断必定先于执行完毕
- 对象:构造等操作先于finalize
小结
事件三大件
- 顺序:事件拆解以后,有些地方存在强耦合顺序关联,但是有些毫无关系
- 执行:事件的执行者可以存在多个
- 资源:多个执行者之间公共资源,获取和保证资源完整
对应名词
- 同(异)步:事件顺序关系
- 并发(行):执行事件人员
- (非)阻塞:资源完整问题
JVM
处理
- 原子性:事件的逻辑完整性
- 可见性:资源的更改一致性
- 有序性:事件的依赖顺序性
拆解步骤
- 事件之间需要参与每一步?同步:异步 (事件无挂部分,可以自行完成)
- 执行者只能有一个?并发:并行 (多个事件同时发生,要么切换着做,要么多个人做)
- 依赖同一份资源?阻塞:非阻塞 (一人持有,只能排队,无锁情况,也只是能观察,而非直接操作)