1、悲观锁和乐观锁
1、synchronized关键字与Lock等锁机制都是悲观锁:从字面意思来理解就是说线程在操作数据的时候是很悲观的,所谓这个“悲观”是指它认为一定会有其它的线程与我争抢,所以为了保证我操作数据的安全,“一定”是先上锁再来操作数据,避免其它线程干扰我。所以它的特点是无论做任何操作,首先都需要先上锁,接下来再去执行后续操作,从而确保了接下来的操作都是由当前这个线程来执行。AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转化为悲观锁,如RetreenLock
2、乐观锁,顾名思义,就是很乐观,每次取拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用情景。java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
2、常规情况下如何保证原子性?
package com.concurrency2;
public class MyTest5 {
private int count;
public /* synchronized */ int getCount() {
return count;
}
public synchronized void increaseCount() {
++this.count;
}
}
JMM关于synchronized的两条规定:
1)线程解锁前,必须把共享变量的最新值刷新到主内存中
2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
(注意:加锁与解锁需要是同一把锁)
使用synchronized(Lock也可以)可以保证原子性,由于++count操作本身不是原子性,因此在该方法一定要加synchronized。
问题:getCount()
方法可以不加synchronized
吗?
答案是:一定要加synchronized
分析:由于increaseCount()
方法加了synchronized
,锁住的对象是MyTest5
的实例,并不是count
这个变量,因此在多线程的情况下,还是可以count
这个变量的值的(使用volatile
往回写的时候则会锁住主内存中count
这个变量),即使线程解锁前,必须把共享变量的最新值刷新到主内存中,在操作++count
的时候其他线程还是可以读取到count
变量
结果:必须得保证两个方法都加了synchronized才可以保证原子性和可见性,也就是说同时间只能有一个线程能调用一个方法,不能同时既能增加,又能读取,这也就是synchronized关键字的问题之所在
可以使用volatile方法保证变量的可见性,使用cas方法保证操作的原子性(重点),AtomicXXXXX类则使用了刚刚说的方式同时实现了变量的可见性,操作的原子性
3、CAS原理
CAS(Compare And Swap/Set)比较并交换,是一个原子指令。CAS 操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时,使用 CAS 操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作
1、CAS的作用位置
image.pngCAS其实是作用在工作内存中的,由上面的图可知,可能会存在某些线程已经对该变量count的值从0变成1了,然后将count对应的缓存行设置为无效状态,也就是++count这个操作在这个线程中相当于没有操作过,并不会知道了该变量是无效状态的然后又回去重新累加过,CAS正是解决了这样的问题,当知道该变量无效便又回去重新计算,直到能把真正的正确结果写入到工作内存)
2、CAS常见的工作方式
image.png伪代码
cas(V, Expected, NewValue) 函数 下面两步是一个原子指令
if(V == E) {
V = NewValue
} else {
try again
}
AtomicInteger的自增操作
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
网友评论