目录
1.锁策略
常见的锁策略有以下几种:
乐观锁与悲观锁
乐观锁的意思就是一般情况下不会产生锁冲突,所以数据进行提交更新的时候才会进行冲突的检查
而悲观锁则总是假设最坏的情况,每次都会把锁锁上,才进行下面的操作
举个例子:
就好像你回宿舍的时候,习惯性的关门,防止老师来检查,这就是悲观锁,而乐观锁就认为不会有人来查的,然后当来查的时候,没关门就G了
这里我们可以看到如果老师最近查寝严格,那么悲观锁合适一点,如果不严格,那么乐观锁更加合适一点,因为乐观锁省去了关门的操作,对于进程也就提高了效率
读写锁
读写锁就是把读和写操作区别对待了,如果多个线程进行读,那么不会产生锁冲突,如果多个线程进行写,那么就会产生冲突
在java中
重量级锁与轻量级锁
重量级锁里面大量使用了用户态到内核态的转换,这个过程是非常消耗时间的
而轻量级锁里面则避免了非必要的用户态到内核态的转换,从而提高了效率
如果锁冲突严重的时候可以考虑重量级锁,因为很多操作都是mutex的,是原子操作
自旋锁与挂起等待锁
自旋锁这个就和循环特别像,就是我当前进程想要获取锁,但是获取失败了,就重新获取,直到获取成功
,在这其中,进程是没用放弃CPU的,所以不涉及线程阻塞和调度,但是缺点也很明显,就是持续小号CPUcpu资源
挂起等待锁就是获取锁,然后失败了,那我就去等待队列里面去呆着,等到有通知说这个锁被释放了,那么我就被唤醒,重新去获取锁,这样虽然可能会慢,但是这是不会消耗 CPU资源的
公平锁与非公平锁
公平锁就是分个先来后到,先来的等前面的线程释放锁的时候,就顺位继承,非公平的就是前一个线程释放锁之后,一起去抢这把锁,谁抢到算谁的
注意:公平锁是需要额外实现数据结构来记录线程的先后顺序的
可重入锁与不可重入锁
可重入锁就是一个线程可以多次对自己加锁,但是不会产生死锁,这种就是可重入锁,不可重入道理一样,JDK提供的线程Lock实现雷,包括synchronized都是可重入锁,而Linux系统提供的mutex则是不可重入锁
2.CAS
什么是CAS?
CAS就是以下的操作被封装成一个指令,也就是原子的
1.比较A与V是否相等(比较)
2.如果相等,将B写进V(交换)
3.返回操作是否成功
其实我们可以近似的堪称是赋值,因为相比较于寄存器B我们更关心内存中是做了什么,就是把B写进B到V中去
CAS应用:
1.实现原子类
2.实现自旋锁
用一段伪代码来理解
while(!CAS(this.owner,null,Thread.currentThread()) ){
}
这里通过比较this.owner和null是否相等,即当前的owner(锁)是否已经被使用,如果是,则通过循环,继续发起判定,如果没有被使用,也就是this.owner == null了,那么就把Thread.currentThread()的值赋值给this.owner,即当前线程拿到了锁
3.ABA问题
什么是ABA问题?
就是当前有一个num,线程1去判断是里面的值是不是A,线程2则把原来的A改成B,然后再改成A,当1判断的时候,并不知道这是不是未被修改过得到值,线程1期待的是未被修改的num值,但是它无法知道是不是被修改过的
解决方案
引入版本号,当CAS在比较数据时,加上版本号,比较值是否符合预期的同时,也比较版本号是否符合预期
当前版本号如果和读到的版本号相同,则没有被修改过
如果高于读到的版本号,则就是被修改过的
3.Synchronized的优化
锁策略
首先我们先要知道Synchronized的锁策略
1.既是乐观锁,又是悲观锁:如果锁冲突频繁,则会从乐观锁变成悲观锁
2.既是轻量级锁,又是重量级锁:刚开始是轻量级如果锁被持有的时间比较长,那么就转换成重量级锁
3.实现轻量级锁的时候,大概率会使用到自旋锁的策略
4.是非公平锁
5.是可重入锁
6.非读写锁
加锁过程的优化
在jvm中,第一次尝试加锁的线程,会被优先进入到偏向锁的状态,即并不是真正的加锁,只是在对象头中做一个标记,如果有别的线程来竞争这把锁,那么就加上锁,如果没有,就不进行加锁操作
有别的线程竞争之后,就会加上轻量级锁,如果竞争还是很激烈,那么就膨胀为重量级锁
锁消除操作
有的程序用到了synchronized,但是并没有在多线程的环境下,那么就可以进行锁消除操作
比如用StringBuffer拼接字符串的时候,如果不是多线程状态,则会把里面的锁给消除
锁粗化
一段逻辑中,如果多次出现加锁解锁操作的时候,那么JVM就会把锁进行自动的粗化,即锁得粒度变大
🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒感谢阅读🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒