00:00
好了,那么咱们接下来呢,再对这个原子型问题呢,再进行进一步的一个分析,那么我创建一个类叫做test,比如说atomic DEMO,对吧?说什么是这个原子性问题呢?咱们首先去考虑一下这个叫做I加加的这个问题,对吧,叫做I加加,说A加加的原子性问题,我们说A加加早在以前咱们讲基础的时候,咱们是不是就讲过呀,我们说我们之前详细的给大家分析过这么一个事,比如说我们说先来那个in I呢等于十对吧?然后呢,我们说做这样一个操作,说I等于I加加对吧?我们说这是不是一道面试题呀,我们说当它运算之后结果是多少,是十啊?我们说为什么A等于I加加,最后的结果是十,我们说因为I加加的操作在计算机底层是不是分为这么几步啊,我们说它产生了一个临时变量,首先呢,是不是把I的值赋给临时变量啊,然后呢。
01:01
A等于I加一是不是在自增一呀,然后呢,他又把这个临时变量的值是不是又负给了I呀,因此我们说实际上这个I加加的操作在计算机底层,是不是说这个互啊,对吧,我们说I加加,我们说最后把I加加的值付给了I,说白了是不是就把time的值付给了I呀,如果说把A加加的值付给Z,我们是不是就把time的值付给Z就完事了呀,所以说我们说A加加它实际上在底层,说加加操作在底层,它实际上有这么几个操作,因此我们说得到一个结论,就是说A加加它实际上A加加的操作实际上实际上它是分为什么呀,三个三个步骤的,分别哪三个步骤呢?首先它是不是有个读啊,然后是改吧,然后是写,所以说A加加实际上分为这么读改写的三个步骤,那么因此咱们针对这个问题呢,接下来咱们搞一个例子,比如说。
02:01
叫做atomic DEMO也让他去employments,来个runable,对吧,然后呢,实现里边的这个抽象方法,那么咱们就搞一个简单一点,咱们就维护一个变量叫int的sirial number,初始值呢为来个零可以,然后呢,紧接着呢,我给他提供一个get方法,叫做s number,这次return呢,Sial number来个加加的操作,那么在这里呢,我们简单点就直接set out,叫做get s number对吧,来个th thad.current th.get name拼上一个冒号拼上叫做get s number,说说白了就调用这个方法呀,对吧,调用get CR number对吧,为了把问题放大一点呢,对吧,或者说咱们搞两秒钟的,呃,0.2秒的延迟可以吧,搞0.2秒。
03:01
的一个延迟,那么这个时候过来我分别要干嘛呢?我要搞十个线程同时去运行,对吧,或者说同时去调用这个方法,是不是每次都加加呀,那么这个时候咱们先看看效果啊,说搞个main方法,然后来个atomic DEMO这样一个创建个实例,比如说ad等于用一个atomic,然后呢,New一个thread吧,把这个ad传过来,点start,但是我们是不是要来个循环14啊对吧,来个循环十次,分别创建十个线程去访问这个方法搞定了,那么这次呢,咱们看看效果,右键运行。嗯,012345678对吧,7801,注意看这个时候已经产生问题了,一一对吧,注意前面这个是线程名啊,后边是不是一和一呀,那么到此时我们是不是已经发现有这个原子性问题了呀,产生你当十个线程去访问这个A加加的时候,它产生了重复数据,那或者或者说我们说可以再来一次,刚才这个线程名呢,可能让大家看的不太清楚,咱们把这个线程名干脆给他删了得了,那么右键再来个运行。
04:13
01234这次没问题对吧,当然我们说多线程安全问题,它并不一定是每次都发生,再来一次。再来一次。再来一次对吧,这也没问题,再来一次,哎,这可有了,对吧?这时候0066啊对吧?那这明显是产生了多线程安全问题,或者说这是原子性问题,我们说这个问题它是怎么产生的呢?那么首先咱们过来呢,再进一步分析一下,我们还是说当呢GM1运行对吧?说它是不是为每个线程都分配一块这个独立的缓存的对吧?都会从这个主存中读取数据,比如说这是我们的主存对吧?然后相应的呢,现在呢,我们有多个线程同时去访问这个储存对吧?同时去访问这个共享数据,说白了同时去访问共享数据,比如说这个呢,是我们的线程一线程一,这个呢是我们的线程二对吧,线程二,那么现在呢,主存中有个什么数据呢?是不是叫做s number这么一个数据啊,对吧?S number这么一个数据,它刚开始的值为。
05:23
零刚开始的值为零,那么接下来呢,我们说当对它进行这个c number主持中的数据进行操作的时候,我们说是不是首先把这个数据先读到我们的线程中来啊,说如果线程一获取了资源,先把这个数据是不是读到我的线程中来啊,也就是要进行A加加的个运算吧,那么比如说现在呢,我是这样读的,叫做int的一个type,是不是产生个临时变量A加加的操作对吧?那这个时候呢,Type等于叫做sial number吧,那么咱就简写一点叫SN可以吧,就简称这个c number,别写那么复杂了,是不是SN先读过来呀,然后呢,紧接着再算什么呢?再算是不是SN等于SN加一这么个操作,是不是SN再次正一,对吧?因为它这个连值变量是要把这个变量是不是付给别的什么,付给I呀Z呀什么什么的,对吧,对吧,那就不管,所以说现在我自增的话,是不是有这么两步操作呀。
06:21
那么在它到运算了之后,说白了就是SN等于SN加一,那么是不是就SN等于一啊,它将要把一是不是写到组织中去,我们说如果说在这个过程中,他正在写的时候呢,那有可能被线成二是不是也读到了呀,也就是说他也把这个c number的值读过来,等于零的时候是不读过来啊,说在他写之前,它被它读过来了,那这个时候呢,C这个线程二是不是也做了这么一个操作,也到这了。对吧?因此在这种情况下呢,我们说它这个时候线程一把它打印同时是不是那个把它写过来,等于一同时打印呢?那这个时候它也写过来,是不是也是一也打印了,那么这样的话是不是就产生问题了呀,对吧?那当然说有同学说说,哎,那这不是刚才我们说内存可见性问题吗?那么我们说我们说内存可见性的问题的解决,我们可以用volaile关键字是不是解决呀,但是我们说原子性这是两步操作呀,对吧?VPI它不能保证说它没有互制性,也就意味着你每次你说可以多个线程同时去对它改,那有可能什么呢?也就是说如果你用wall tell修饰以后,那无非也就是这个操作呢,它是在主存中进行的这个操作呢,是不是也是在主存中进行的呀,那也就意味着当你第一个线程准备对它写一的时候,是吧,那是不是准备写一啊,那这个时候它是不是也在写之前对吧,它是不是也读到了,读到了之后。
07:52
八是不是也只能写一啊,说这个问题是不是依然存在呀,得到的结论就是因为哀加加他有读改写三步操作,所以说它只能说保证内存可见性问题,但是呢,不能保证原子性问题,对吧?原所有的原子就是你这两步明明是不可分割的对吧?但是呢,它现在是不是分割开来了对吧?读改写是不是都分开来了,所以说你即便用过勒太要修饰了之后,它这个内存可见性问题依然存在。
08:28
那么因此我们说怎么解决呢?我们说先解决之前我给大家演示一下说什么用volaile对吧,Voile修饰,我们说这个问题存不存在,右键运行是不是依然存在啊,那因此我们说那有了这个问题我们该怎么解决呢?那么第二种解决方式,那就叫做原子变量,我们说在JDK1.5以后对吧,JDK1.5以后对吧,给我们提供了那个叫做z UC java.u.coner的包吧,对吧,叫java.uQ点叫做con con CU con里边有个叫做atomic包对吧,这个包下包下提供了对吧,大量常用的常用的原子原子变量,原子变量什么是原子变量呢?咱们简单的看一眼API,我们说在java.u to.coner.atomic点进来,这里边是不是给我们提供了大量的常。
09:29
用的原子变量啊,比如说有布尔啊,有in teacher啊,有long,这是不是都是常用的对吧?相应的呢,它还有对应的数组,叫做in teacher array long array等等,这个数组是什么意思呢?就是它保证里边的每一个元素都是保证数组中的每一个元素都是原子,原子性,具有原子性的对吧,也具有内存可见性搞对吧,对吧,那这是几个相应的是不是?还有比如说你要有对象的引用,是不是atomic response啊,那就是他们的使用方式呢,就相当于跟我们原来那个integer了,玻璃的包装类使用方式很类似,里边都有对应的方法,那么我们说,那这个原子变量它是如何解决刚才咱们说的这些问题的呢?如何对吧解决这些问题的,那首先我们说原子变量,它包含什么呢?一它有这个volo t的特性,它里边所包装的封装的值都用volaile修饰,我们可以稍微的看一眼atomic,以这个inte为例,点进去看。
10:29
说它里边封装的变量是不是都是VO的,有了这个VO tell,它保证了一个什么问题啊,哎,那就是原子可见性,说VO tell保证原子,呃,不是不是不是叫做内存可见性,对吧?有了all是不是保证内存可见性呢?相应的它还有什么去保证原子性呢?注意注意,它用了叫做CAS算法保证对吧,数据的原子性它是有这两点,除了涡派以外,它还用了CS算法保证了数据的原子性,我们说那什么是CS呢?叫做compare and swap对吧?Swa保证到原子性,那大家问题就来了,对吧?说那这个CS算法又是一个什么东西,实际上之前大给大家是不是都提到过呀,那么咱们现在呢,再进一步说一下,我们说这个所谓的CS算法呀,这个算法呀,它是对吧,是操作系统。
11:29
嗯,对于或者说是硬件,硬件对于并发操作共享数据的共享数据的一个支持,说白了就是硬件对于这种并发访问共享数据的一个支持吧,也是计算机底层的支持,只不过我们GVM对于F,呃,CS算法也做了一个基支持,我们说这个GPM,或者说我们的一些类底层大量的用到了这个CS算法,对吧,它本身呢,是硬件对于并发的操作,只不过我们的GPM对它也是支持,那我们说到底什么,什么是CAS呢?我们说这个CAS啊,它包含了说包含了三个操作数,哪三个呢?一个叫做内存值,比如说我们叫做V,对吧,一个呢,叫做预估值,或者说就值也行,叫预估值,也可以叫做A,相应的还有一个叫做你。
12:29
要更新的值,更新的那个值,比如说是B,然后它有个什么特点,就说当且仅当V的值等于A10对吧?当且仅当V的值等于A10,那么才把B的值赋给V对吧?说当写仅当这个V和A相等时,那么它才会把B的值赋给V,那么否则呢?说否则,嗯,否则将什么操作都不做对吧?将这个不做任何操作,否则将不做任何操作,那这就是CAS算法的一个特性,首先只有当V等于A时,然后才会把B的值赋给V,否则什么操作都不做,那说这怎么就能避免这个叫做原子性问题呢?咱们回来过来进一步的对这个看一下。
13:29
呃,我们的图在这里对吧?那么首先呢,这个这个操作是不是最后要写值,写值为一过去呀,对吧?咱们把这个步呢,就给它先删掉,那么首先所谓的CS,它有这么几个操作的步骤,或者说我们把这个呢,先放这一边对吧?先放这一边对吧?那么说它有这么几个操作步骤,首先它会读取一个内存值对吧,读取个内存值V,那么首先它从主存中读取这个内存值是V,也就是零读过来呀,对吧?然后紧接着我们说CS compare and swap,那是不是叫做比较和替换呢?那么它要替换的时候,它也会再次读取一下原来的旧值,对吧,也是读取一下原来的旧值,那么这个旧值呢?那A,那这次它一读是不是也是零啊,对吧,也是零,那么这时呢,B经过B1运算的以后,那SN最终一运算是不是最终的结果是一啊,最终他要把一想着要写过去吧。
14:29
那这个时候呢,我说B呢,这个时候等于一大家要注意,实际上CS算法呢,你可以把它理解为两步操作,首先读取内存值,这是一步,第二步你要比较和替换,这是一步,并且呢,你可以把他们认为这都是叫做同步的,就一次只有一个,是不是能访问呢?对吧,一次只有一个能访问,那么这个时我们说当这个第一号线程在准备操作的时候,那是不是有可能二号线程也来了呀,二号线程也来了之后呢,它也是经历这么几个操作,首先第一步它是不是读取一个内存值啊,比如说等于也是零,刚开始它获取这个内存值,获取过来的话,是不是也是零,就有可能也是零,这头还没写,这头它就获取了,也是零吧,那这个时候二号线程是不是也准备改,那于此他要改的时候,有可能他是不是就成功了呀,一成功他把原来的零,那就变成了。
15:25
一对吧,我把它删掉删掉,那么这个时候它的零是不是被它成功为一了呀,为什么成为一,因为V和A是不是相等了,如果V和A相等了的话,那它会把B的值是不是修改更新写过去对吧?那么这个时候呢,他刚才读到的线程二刚才读到的对吧?内存值是零,然后下一次呢,它在它在将要进行叫做叫做比较和替换的时候,那这个时候呢,它一读再次读取你预估的值,这次一读过来的是不是叫做A1呀,这一次它一读一,那么与此同时它要相较于原来的内存值要改的话,那它这次你要改的值是不是也是也是一呀。
16:14
对吧,那如果说你不管是要改起对吧,那现在呢,我们说这也是两步操作,首先零和一相等嘛,对吧,那说不相等啊,那不相等的话,此时它将什么都不做,将什么都不做,所以说他利用这种方式呢,解决了这种原子性问题,首先你完全可以把它们是不是看成两个呀,先读取一次内存值,然后呢,再进行compare and swap是不是进行。替换呢,叫做比较并设置,或者比较并替换的一个操作,对吧?并且这个呢,一访问的时候只有一个线程可以进来,因此我们说得到一个结论是什么意思呢?说这个CS算法,如果说当多个线程并发的对组成中的数据进行修改的时候,那么有切是不是只有一个线程会成功,其他的线程是不是都会失败呀,对吧?其他的线程都会失败,只有一个会成功。
17:13
但是我们说CS算法效率比原来的锁就高吗?它就高,为什么呢?注意因为我们说CS算法如果当这一次不成功的时候,他下一次他不会阻塞,也就是说不会放弃CPU给他的这个经营权,他紧接着可以立即再去尝试去更新,所以说CS算法要比普通的那种同步锁的效率要高很多。对吧?那我们说CS可能也有缺点,缺点什么呢?缺点就是我们自己要写的算法比较多,比如说我说一旦失败了之后,我们怎么做,对吧,是不是通常用循环呢?一旦失败了是不是再来,一旦失败了是不是再来,什么时候成功是什么时候为止啊,那这就是CAS算法,对吧?没关系啊,一会大家对这个CS不了解,咱们可以稍微写个代码,对这个CS啊,稍微模拟一下,大家看看效果就理解了,那当然咱们也只是模拟,这是不是都是人家计算机底层,也就是硬件对于并发操作的一个支持啊,把这个了解了解明白这个原理就可以,那么过来呢,咱们利用这个原子变量呢,把这个问题再解决一下,对吧?原来我们说用这个sial number是不是不行啊,现在得用原子变量,原子变量呢叫做atomic,是不是叫integer啊,对吧,然后比如说叫做serial number等于new一个atomic integer,注意它就相当于原来的咱们那个包装类,使用方式几乎都是。
18:41
啊,只不过他给我们提供了一些是不是特有的方法呀,比如说什么方法我们过来试试,将下来我们是不是要算加加操作呀,加加操作那只需要要CE number点上注意看,比如说叫做get,叫做get increment对吧,那这不是不是叫做获取并自增对吧?相应的上一个叫做get Andre,那是不是叫做获取并递减,然后你下其他的,你想加就加,是不是想设置就设置新值啊,想设置新值就设置新值,那这是获取并递增和递减,那还有比如说来个叫做rement and get。
19:18
那这叫啥?哎,那这叫先自增,先递增,是不是再获取啊,就是区分前加加后加加的吧,说白了呀,对,实际上他们呢,还有一个比较核心的一个方法叫做compare and set,那这就是刚才咱们所说的那个叫做比较和替换,你首先是不是得取原来的内存值啊,把你要更新的值过去吧,然后呢,当多个线程都来操作的时候,那是不是有些只有一个能成功就搞定,其他的使用方式呢?就跟我们说这个原来用包装类一样,都是怎么个用,那现在我们要是不是后加加呀,那就可以get and improvement,注意就这么点改动,是不是变成了原子变量,那这次呢,再去右键还是十个线程吗?执行是不是1230123456789啊,再来01234567890,再来再来再来对吧,就没有问题,那这样的话就是原子变量对吧,首先解决了内存可见性问题,然后呢,又具有这种保证数据的原子性问题,这。
20:20
的就是叫做原子变量,相应的API下是不是给我们提供了大很多种的原子变量啊,当我们需要的时候拿过来用就可以用的方式,查查API,看看里边的方法就可以,那么大家对这个CS算法可能稍微的不太了解,咱们用几分钟时间对吧,模拟一个这么一个操作对吧,让大家进一步的了解,那么这个呢,先到这。
我来说两句