一、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方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则更新。