00:00
接下来给大家介绍一下如何正确使用V,也即V使用的最佳实践,或者说在面试过程中会问你,嗯,你对,也了解了,也达得出内存屏障了,那么请问实际工作中你觉得外应该用在哪?也就是它的适用场景有哪一些?好,那么首先我们来看一下。对于单一赋值的情况,V绝对是什么最佳人选哎。单一赋值绝绝子,但是复合运算这样的啊,有连带操作的,那I加加加加I这样的千万不要用,那么什么叫单一赋值这样呢?就是说同学们请看。V in a等于12,或者V布尔flag是true还是false?因为我们都晓得,只要被V修饰的进程牌加可见性,也即只要主物理内存里面那个共享变量一旦被改变了,它是不是会马上对其他线程可见,那么自然而然其他线程都见到了,也即反过来讲,其他线程都被通知到了,及时有效性是不是获得保证,尤其这个flag这样的操作就是假设在高并发里面。
01:06
如果你这个是靠变量来通知其他现场来改变后续动作的,那么尽量把它定义为tell,那这样的话可见性保证,以便大家都知道。非常的高效,好,那么下面就是状态标志,判断业务是否结束,那么这个呢,我们就跟前面一样啊,这个呢,我们已经说过了,代码也编过。这个tell修饰了这个flag,假设啊,线程1FLAG初始读的时候是错,那么他可能会一直在这well干活,但是呢,过了一段时间以后,根据我们的系统变更或者微服的调用TR,这个线程已经把这个flag从默认的错改为了false,并写回了主物理内存,那么其他线程是不是马上就可见?那么这个时候,如果这个flag从默认的处。读到了已经被修改为的force后续,那么他是不是马上跳出循环结束该线程的操作,所以的使用过程当中作为一个单一的布尔状态标志。
02:04
只要发生了一次重要的这样的一个事情,就是初始化完成了,或者某种任务结束,需要下一个阶段来续接了,那么对于这种判断业务是否结束,它的有效性是最佳的,也是最快的好。这是一个第二个。对于开销比较低的读,那么我们的写索策略也可以用这个VE配合我们的S,那什么意思呢?单独远多于。写,那么结合我们使用什么内部组合V来减少同步开销,那么大家都清楚,如果int value,我们要保证原子性,那么最土的方法,最安全的方法,那么是不是?Get value和get是读取操作,Increase是写入操作。那么也就是说,一般我们要是用最土的方法干脆。两个方法都加snchize,没错,绝对是OK的,但是大家都清楚,如果是什么读多写少的话,那这个时候读你是加SNCH了,你加了size以后,这个时候我们的安全性提升,但是并发性是不是下降啊,对吧,所以时候我们尽量呢减轻这样的重所那么所以说我们可以在写的时候用tell利用,呃,写的时候用这个size。
03:24
利用SYNCH保证符合操作的什么原子性,但是读的时候我们没加锁,读的时候只要一改完了,我们马上就会收到最新的效果。最新的通知这个可见性对吧,我们利用V来保证读取操作尺码可见性,那么这样的话可以给我们呢,在这种场景下,那么我们利用V和S来减少我们的开销和系统的消耗,那么第三种情况。也就是我们在写。单利模式。Double切克双端索得发布的时候的话,那么这个。V用的是非常多的,那么单利模式是什么?我么?我默认大家都清楚对吧,我在废话,但是呢,当要写出一个什么呃高并发下面的重要的一个单力模式,那么传统的懒汉和恶汉可能呢,稍微呢还有心有余而力不足,所以呢,我们一般啊会写了一个这样一个版本,那么大家请看,对于我们的就是进去以后两次加锁成为双端检索哈,待会我们看,那么我们再看在加YT和不加YT,那么它们两个的效果会有什么问题,咱们该怎么解决,那么下面请看。
04:33
大家请看。此时我们写了一个。双端检索,那么首先来说一下什么叫这个double切啊,俗称这个DC,这个L呢,就是lock加锁,那么它是这样的啊。进来之前我们先判断一下。OK,然后立刻加锁,加完锁以后再判断一次,那么就是在这把锁之前和之后判断两次,就俗称double切好,这是我们传统的一个什么比较安全的。
05:06
用双端检索的单立写法,那么首先啊,我们在双重锁的设计以后,在多线程并发创建对象的时候,会通过加锁保证只有一个线程来创建对象,这些都没问题,关键是在这儿。请看,此时我并没有加外。那么这在这块就会存在一个安全隐患,就在多线程的环境下面,由于重拍线,该对象可能还没有完成初始化的时候就被线其他线程给读取了。那么。我们进来,虽然说这是那没问题,也加锁了没问题,这是个难也没问题,但是这个时候我引用指针一生存一个空,申请一个内存空间第二个。给这个空间赋值这个实对象,然后再把这个指向指向它,但是在多线程环境下面重排序。你没交来tell,可能会导致你还没有完成初始化呢。
06:00
我就有已经有指针指向它来使用,那么可能读出来的不是这个对象就会变成个烂。好,我们这是什么意思呢?来,同学们。我们单线程来看一下这个问题代码啊。正常情况下,或者单线的环境下面,在我们刚才说那个隐患那块啊,会进行这样的操作啊,再来看一遍,所谓的隐患就是这儿啊。我们就就就这一行,那么他要三步,先申请空间,再把new的这个实际对象放到我们申请好的空间里面,然后再把这个引用指向这个空间,那么这个时候如果步步顺利有空间,并且这个空间上也有这个对象,也有引用指向这个空间,那么就可以读到这个对象挺好的,对吧,相当于说先有个坑,再放个萝卜,然后呢,我再去这个坑上抓这个萝卜。OK,但是由于是没有Java的T,我们在单线程看问题代码的时候,我们正常情况下。第一个。分配对象的内存空间,就是我们说的先有个开,第二个初始化对象栽上这个萝卜,第三个再把这个实例附过去,那么设置instance指向刚刚分配好的内存地址,此时如果是这个顺序的话没问题,一个坑一个萝卜,引用指向这个坑并得到这个坑里面的萝卜。好,但是假设你没叫巴来太存在着指令冲排序,那么在多线程的环境下面就有可能产生这样一种情况。
07:26
如果正确的情况,它会导致指令重排。他自作聪明,那么。导致重排序就是这儿的不再是123,变成了132,那么相当于说来先有个坑,但是这个萝卜还没放进去呢,这个引用非常着急的。自作聪明就已经先指向他,就会导致此时对象还没有初始化完成,这个萝卜还没放到这个坑里面呢,虽然说我已经确实溜出来这个萝卜了,那会就会导致我们什么其他线程得到的还是个烂,因为它是初始化还没有成型呢,你呢心急吃不了热豆腐,现在的话呢,申请坑,坑里面有萝卜,然后你再来找到这个摊位取萝卜,结果你现在呢,太着急了。
08:12
申请坑OK了,萝布还没初始化呢,你就马上要,要这个时候这不是指向了一个空的坑,就会导致烂了吗?所以说我们在著名的双端检查当中,假设我们呢,不加班的太就会产生这样的一种东西,那么某一个线程就可能会获得一个没有未完,没有实例化完成的一个初始化对象,那么这个时候导致这一块还会是个烂。所以呢,我们在这块呢,就要来进行一下相关的解决,来一定要通过来声明实现现场安全的。延迟初始化,OK,那么就是不要重排,那么下面。这个隐患,那么怎么解决这个隐患的原理呢?就是利用v tell禁止初始化对象和上面的设置这个指向内存的空间来进行重盘,一句话,你先等我这个坑里面确确实实有萝卜了,你再来弄,你别捷足先单OK。所以说在这我们要通过来tell来保证双端检索这种。
09:12
多线,呃,单立模式,多线程下的单立模式才是最安全的,所以说我们呢,解决方法就是通过将V来修饰,那么在这些环境下面就是我们正确使用套的一种场景案例,那么面试的时候可能同学们会用得到,大致跟面试官说一下。
我来说两句