CAS是什么
其全称是 Compare And Swap(比较再交换) 是一种乐观锁思想,在无锁情况下保证线程操作共享数据的原子性。
现在主内存中有一个 V: int a = 100, 在两个线程进来同时操作,
线程1 进行a++操作,对于线程1来说,有两个值,一个是修改之前的,一个是修改之后的,分别用 A,B表示
A:int a =100
B:int a = 101
那么同样的,线程2进行a--
A:int a =100
B:int a = 99
现在就是拿A的数据和V进行比对,如果一样就成功,不一样则失败。 可以确定线程1先比对的话,会成功,但是此时 V会修改为 a=101, 此时线程2再去比对,由于线程2的A值仍然是原始的100,将操作失败。开始自旋。
底层实现
依赖操作系统提供的Unsafe类直接调用底层CAS指令。
什么是自旋?
当线程2操作失败后,将重新走一遍一开始的流程。即将V值重新复制一份,再操作 例如现在经过线程1的操作,V值是101,那么线程2 此时将 101 作为自己的A值,如下
A:int a =101
B:int a = 100
synchronized
如果有十张优惠券,被用户争抢,现在只有1张了。那么此时如果有三个线程同时进来,同时判断依然有优惠券, 一起抢,就会造成超卖现象,即优惠券剩余数量成为负数。
synchronized 是一个对象锁。使用它可以确保同一时刻至多只有一个线程持有对象锁,其他线程被阻塞。
那么synchronized底层如何实现的呢?
就是一个Monitor。通过 javap -v xx.class 查看字节码文件就可以看到汇编信息了。
5 monitorenter
6
7
8
9...
15 monitorexit
出现的信息中会出现以上内容,monitorenter和monitorexit之间是锁住的内容
工作机制
monitor称为监视器,是jvm提供,c++语言实现的。
他的结构
分别是什么意思呢?
当使用
synchronized(obj){
}
时会将obj与monitor关联,首先判断monitor中 Owner是否是null,如果是,那么当前线程拥有对象锁。切Owner只能关联一个线程,再来一个线程,由于线程1已经绑定Owner,线程2就不能获取锁,只能进入entryList中进行等待。等待的线程就处于阻塞状态。
如果线程1释放锁,Owner变成null,就会唤醒阻塞的线程,让他们争抢Owner所有权。
当线程调用wait(),进入waitset中等待唤醒。
ReentrantLock
它的效果和synchronized一样,区别在于
bollen b = lock.tryLock()
if(b){
}
这个可以尝试获取锁,返回布尔值,在此基础上进行判断,如果获取到锁就执行,没有获取到就不执行。
而synchronized是一定要获取到锁的。
实现原理
Reentrant翻译过来是可重入的意思,
ThreadLocal
是线程隔离的,例如分别在线程1和线程2中操作ThreadLocal,ThreadLocal的值是相互不影响的。
作用
如果一个处理流程 t1 中需要接收user作为参数,获取用户权限做一些处理。如果类似的处理流程有很多, 每次都需要去拿user对象就非常麻烦,怎么解决呢?
如果定义一个 static 变量可以在单线程情况下解决,但是并发情况下就会出现问题,不同的线程都在操作该变量,将导致混乱。
所以我们需要的不是针对整个程序而言的全局变量,而是对于这个线程而言的全局变量。通过threadLocal来存储user,每次需要时从threadLocal获取,这样就省去了参数传递的繁琐。
实际案例
我们需要获取用户权限做一些事情。
先定义一个过滤器,这样所有请求都会经过过滤器。在过滤器中定义 static threadLocal对象,重写dofilter方法, 设置threadLocal值为0,判断是否有权限,有权限就设置为1。
这样在我们自己写的controller类中,只需要获取threadlocal的值,判断是否是1来确定是否有权限就可以了,而不用不断传递参数。
可能的问题
由于使用的是线程池,所以线程下次可能被其他请求使用。所以最好在使用后通过remove()将threadlocal进行清理