目录
一、引言
前面的博客我们为了防止线程抢占式执行,我们就引入了锁这个概念,但锁的知识还存在很多,所以本篇博客就简单给大家再介绍一下锁的知识。
二、锁的分类
1.乐观锁 vs 悲观锁
悲观锁:总是假设成最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
乐观锁:假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,就会对数据是否产生并发冲突进行检测,如果发现了并发冲突,则让返回用户错误的信息,让用户决定如何去做。
synchronized初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略。
2.重量级锁 vs 轻量级锁
重量级锁:加锁机制重度依赖了OS提供的mutex,大量的内核态用户态切换,很容易引发线程的调度。
轻量级锁:加锁机制尽可能不使用mutex,而是尽量在用户态代码完成,实在搞不定了,再使用mutex。少量的内核态用户态切换,不太容易引发线程调度。
synchronized开始是一个轻量级锁,如果锁冲突比较严重,就会变成重量级锁。
3.自旋锁 vs 挂起等待锁
自旋锁:线程在枪锁失败后,进入阻塞状态,放弃CPU,需要过很久才能再次被调度。如果获取锁失败,立即再尝试获得锁,无限循环,直到获得锁为止。一旦锁被其他线程释放,就能第一时间获取到锁。
挂起等待锁:第一次获取锁失败,就一直等,过很久才去重新获取。
自旋锁是一种典型的”轻量级锁“的实现方式:
优点:没有放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获得锁。
缺点:如果锁被其他线程持有的时间比较久,那么就会一直持续消耗CPU(而挂起等待的时候不消耗CPU的)。
synchronized中的轻量级锁策略大概率就是通过自旋锁实现的。
4.公平锁 vs 非公平锁
公平锁:遵守”先来后到“。
非公平锁:不遵守”先来后到“。
注意:操作系统内部的线程调度就可以视为是随机的,如果不做额外的限制,锁就是非公平锁,如果要实现公平锁,就需要依赖额外的数据结构,来记录线程们的先后顺序。
synchronized是非公平锁。
5.可重入锁 vs 不可重入锁
可重入锁:允许同一个线程多次获取同一把锁。比如一个递归函数里有枷锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归锁)。Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成Lock实现类,包括synchronized都是可重入的。
不可重入锁:第一次加锁之后,再一次进行加锁,就会出现死锁的情况。
synchronized是可重入锁。
6.读写锁
1.两个线程都只是读一个数据,此时并没有线程安全问题,直接并发的读取即可。
2.两个线程写同一个数据,存在线程安全问题。
3.一个线程读另外一个线程写,也有线程安全问题。
读写锁就是把读操作和写操作区分对待,Java标准库提供了ReentrantReadWriteLock类,实现了读写锁。ReentrantReadWriteLock.ReadLock类表示一个读锁,这个对象提供了lock/unlock方法进行加锁解锁。ReentrantReadWriteLock.WriteLock类表示一个写锁,这个对象也提供了lock/unlock方法进行加锁解锁。
synchronized不是读写锁。
三、CAS
1.什么是CAS?
全称”Compare and swap“假设内存原数据V,旧的预期值A,需要修改的新值B,1.比较A与V是否相等。2.如果比较相等,将B写入V。3.返回操作是否成功。
2.CAS伪代码
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
当多个线程同时对某个资源进⾏CAS操作,只能有⼀个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。CAS可以被视为一种乐观锁。
3.CAS的实现
1.Java的CAS利用的是unsafe这个类提供的CAS操作。
2.unsafe的CAS依赖的是jvm针对不同的操作系统实现的Atomic::cmpxchg
3.Atomic::cmpchg的实现使用了汇编的CAS操作,并使用cpu硬件提供的lock机制保证其原子性。
4.CAS的应用
1.实现原子类
提供了java.util.concurrent,atomic包,里面的类都是基于这种方式实现的。
AtomicInteger atomicInteger = new AtomicInteger(0);
atomicInteger.getAndIncrement();
注意:CAS是直接读写内存的,而不是操作寄存器。CAS的读内存,比较,写内存操作是一条硬件指令,是原子的。
2.实现自旋锁
5.CAS的ABA问题
存在两个线程,即当t1想把某个值改为Z时,先判断num是否为A,再改为Z,t2线程又把其从A改成B,又从B改成A。
ABA问题的解决:
给要修改的数据引入版本号,在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期,如果发现当前版本号与之前的版本号一致,就真正执行修改操作,并让版本号自增。如果发现当前版本号比之前读到的大,就认为操作失败。
四、总结
本篇博客简单介绍了锁的分类以及CAS锁,欢迎大家在评论区讨论,感谢大家观看!
标签:重入,初阶,synchronized,--,JavaEE,CAS,vs,线程,加锁 From: https://blog.csdn.net/m0_74815183/article/details/139392266