Java并发编程读书笔记——volatile、synchronized和原子操作(CAS机制详解)

一、volatile

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性。

1.当一个共享变量被volatile修饰时,它实现可见性的原理如下:

当对被volatile修饰的变量进行写操作时,Lock前缀指令会做两件事情:

    (1).将当前处理器缓存行(工作内存)的数据写回系统内存(主内存);——store屏障指令

    (2).这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。(即每次对volatile共享变量进行读操作时,都会从主内存当中重新读取到处理器缓存里)——load屏障指令

二、synchronized

    1.执行同步代码块的流程:

        (1).获取互斥锁(synchronized括号里配置的对象);

        (2).清空工作内存;

        (3).从主内存拷贝变量到工作内存中;

        (4).执行代码块;

        (5).把工作内存中的修改后数据刷到主内存;

        (6).释放互斥锁。

     一个线程试图访问同步代码块时,它首先必须先得到锁,退出或者抛出异常时必须释放锁。

三、二者的区别

    (1).volatile的本质是告诉Java当前变量在工作内存中的值是不确定的,需要从主内存中读取;synchronized是锁定当前变量,只有当前变量可以访问;

    (2).volatile作用于变量级别,synchronized可以作用于变量,方法,类;

    (3).volatile仅实现变量的可见性,不能保证原子性;synchronized既可以保证原子性,也可以保证可见性;

                典型案例:i++;  

                i为被volatile修饰的共享变量,假设默认值为1;当两个线程执行i++时,得到的结果可能还是2,只保证两个象常读取 到的值是最新的,但是没有保证i++这个操作的原子性,解决的办法是使用synchronized关键字。

    (4).volatile不会造成线程阻塞,synchronized可能会造成线程阻塞。

四、偏向锁、轻量级锁

书中还讲到了偏向锁和轻量级锁,感兴趣的同学可以自己去深入的了解一下。

五、原子操作

        1.定义:不可被终端的一个或一系列操作。即要么都完成,要么都不完成。

        2.Java中是怎么实现原子操作的?

            在Java中可以通过锁和循环CAS的方式来实现原子操作,锁就不用多说了,这里我主要想记录一下CAS:

        3.CAS机制:(非常重要,后面很多地方都涉及到)

                CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

                CAS中使用了3个基本的操作数:内存地址V,旧的预期值A,要修改的新值B。当更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将对应的A修改成B。

          下面详细说一下:

                1).在内存地址V当中,存储着值为10的变量

               2).此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。

               3).在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

               4).线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

              5).线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋

                6).这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

                7).线程1进行SWAP,把地址V的值替换为B,也就是12。

                从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

        4.应用到CAS的地方:Atomic系列类、Lock系列类的底层实现等等。

        5.CAS的缺点:

               

            1.CPU开销较大

            在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。         

            2.不能保证代码块的原子性

            CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

            3.ABA问题:

                因为CAS需要在操作值时,检查值有没有变化,如果没有变化则更新。但是如果一个值原来是A,后来变成B,又再次变回A,那么CAS机制就不能检测到值得变化了,这就是ABA问题。

                解决思路:添加版本号,即每次更新的时候版本号加1。   从Java1.5开始,Atomic包下提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则更新。




猜你喜欢

转载自blog.csdn.net/crazer_cy/article/details/79633277