00:00
好,同学们,接下来我们完成了上面的总则和八条以后,要对happen before先行发生原则做一个小总结。其实呢,也不难来。就Java语言而言啊,Happens before先行发生原则战是一种什么可见性,说白了就是谁前谁后,大家顺序别乱了,秩序不混乱,那么这个时候结果明确,后面的可以看到前面的执行效果和结果,那么决定我下一步该怎么做,程序就不容易乱。所以说A先行发生于B,意味着A发生过的事情对B来说都是什么可见的,无论A事情和B事情是否是发生在同一个线程里面。那么GMM的设计呢?是分为两部分。一部分是。面向程序员提供的,也就是说底噪要保证happens before,它自己给你搞定了。他通俗易懂的向我们程序员阐述了一个什么强内存模型JMM,对吧,就是哦,我就明白了,反正一句话,呃,大家呢,就是对多线程对内存的访问,就是符合这个内存模型GMM,那么我们只需要理解进行发生原则,编写并发安全程序就行。第二个是针对GM。
01:07
那么呢,为了尽可能少的对编译器、处理器做约束,从而提高性能,那么GM在不影响程序执行结果的前提下,对其不做要求,就是说允许你什么优化,自己去玩指令重排,只要你结果别没我搞错对吧?哎呀,说难听点,三天以后你给我交作业,你是三天每天都做一点,还是第三天最后的deadline你自己一口气上线,我不关心了,随便你安排工作。你只需要给我结果就行,所以说我们程序员呢,只需要关注前面的就行,我们理解了happen before,它为什么我们写这些并发程序,不需要你每次都写完来才有变量,每大部分情况下都是你怎么写的这个程序怎么落地,执行的顺序都跟我们所设想的一致,因为底层有还比before给你保证,那么其他繁杂的内容,GM规范也给你做了约定,比方说我们的总线。协议或者是底层的这个总线嗅探机制啊,这些我们就不再过多的展开,太底层偏操作系统和硬件了,那么我们呢,就写好代码就行,好,那么接下来。
02:10
我们来看一下我们的案例啊,最后来复习一下,那么来同学们现在有这么一个动作。最普通的一个类,它里面定义了一个变量value等于零。Set get get嘛,就获得它的值啊嘛,就做一个原子操作加加,那么首先我先问大家一嘴加加I加加或者是加加I,它这个在没有加SYNCH的前提下,是不是原子操作好,假设存在线程A和B啊,A跟B。先传I。时间上啊。先调用了set value,那么是不是零变成一?然后线程B又调用了同一个对象的get value,那么线程B收到的返回值啊,请告诉我是零还是一?那么结合前面所讲,那么你应该现在回答我是什么?答案是不是不一定啊,那么所以说我们在这。
03:01
我们来给大家做一下解释。首先啊。我们呢?A和B2个线程,A先调B,随后去set b去get,那么线程B收到的返回值是什么?那么这个时候我们呢,就按照我们的happen before的规则啊什么的,那个5678呢,我们就先。不再展开啊,就说八条里面的话,就没有牵扯到什么这些中断啊,终止啊,这些我们就说次序锁定和传递前四条就可以看得出了啊,那么来同学们。第一个,由于两个方法是由不同的线程调用,不在同一个线程当中,所以肯定不满足程序的次序规则,OK,你别忘了程序的次序规则,我们这儿说的是什么,一个线程内。按照代码的顺序写在前面的操作,先行发生于写在什么后面的操作,哎,这个你要给我切记了,好来,那么接下来第一个规则不满足,次序不满足。第二个两个方法都没有使用锁有没有什么lock,按lock或者SYNCH,我们晓得这个是什么非原子操作,所以没有满足锁定的规矩。第三个变量没有用为来tell修饰啊,所以为来tell变量的规则也不满足。
04:14
第四一个传递规则肯定不满意啦,只有两个嘛,不牵扯到第三个,所以说。我们无法通过happen before的原则推导出A1定会先行发生于B,虽然可以确定在时间上不一定,它可能先是A到这儿,它才执行到这儿呢,它突然被挂起了,后来者居上,他去获得的这个value就是零,其实而言,我是希望你获得多少一,所以说我们无法确认线程B获得的结果是什么,所以这段代码不是线程安全的,该怎么修复啊。那么自然而然,兄弟们以及大家的基础第一种。一不做二不休,把s get的方法都定义为synchize,就是加锁,那么这个时候只要有synchize什么都满足了,因为同一时间段尤其仅有一个去反问,所以说同学们改吧改吧,那么假设这面有个类啊,同学们可以去执行一下,那不用讲,那么这个肯定是现场安全的方法,都加锁了,就跟卖票一样的,对吧,我们卖票点一下减一张,这个是点一下加一张。
05:10
一回事,那么这个时候能不能解决,当然能解决了,但是这个好不好?不好,为什么,太狠了,太重了,你这个就是一刀切写操作加SNCH加锁,100%需要,因为保证数据一致性,但是读操作呢,同一时间段你只允许什么一个人来读啊,那这个时候是不是程序安全性获得了极大的提升,但是并发性获得了下降啊,所以说第一种方法我们能解决。并发量小,你就用它。但是并发量一旦上来了,对不起,我们呢就要用第二种方法,那么什么意思啊,尽量少用thinknchize,所以说第二种我们来设定,把value定义为变量tell,我们后面会说它其中保证一个东西叫可见性,一改马上就可以获得通知,那么set方法对value的修改不依赖于value的原值啊,因为我每次啊都是会加加对吧?所以说满足y tell关键字的使用场景,就是说你一修改我就要看到我们,所以说同学们,我们在这儿就做一下。
06:08
修正。第一个。利用tell的保证读写操作的什么可见性,把这个普通的实例变量前面加关键字tell,利用S保证符合操作原子性,结合我们使用锁我来跳变量来减少同步的开销,所以说我们在这儿。这个你躲不掉,因为我们必须要用SYNCH保证符合操作的原子性,所以说这个方案写操作给我加SNCH。只能有一个写,写完了才能有第二个,OK,数据不乱,但是读的时候就没有这个必要了,那这个时候就不要说读的时候啊,我们也要是什么同一时间段只有一个人来读那。没有任何意义,对吧,所以说我们要利用tell保证读取操作什么可见性,把这个变量从普通的修改为tell的,OK,所以说通过这两种方法,我们呢,可以修正上面这个程序的小问题,好,那么同学们这个。
07:03
就是给大家证明了GM规范它为什么能保证这些顺序,还结合了happen before先行发生原则。
我来说两句