锁策略,CAS以及synchronized的优化

发布于:2022-10-15 ⋅ 阅读:(374) ⋅ 点赞:(0)

目录

1.锁策略

乐观锁与悲观锁

读写锁

重量级锁与轻量级锁

自旋锁与挂起等待锁

公平锁与非公平锁

可重入锁与不可重入锁

2.CAS

什么是CAS?

CAS应用:

1.实现原子类

2.实现自旋锁

3.ABA问题

3.Synchronized的优化

锁策略

加锁过程的优化

锁消除操作


1.锁策略

常见的锁策略有以下几种:

乐观锁与悲观锁

乐观锁的意思就是一般情况下不会产生锁冲突,所以数据进行提交更新的时候才会进行冲突的检查

而悲观锁则总是假设最坏的情况,每次都会把锁锁上,才进行下面的操作

举个例子:

就好像你回宿舍的时候,习惯性的关门,防止老师来检查,这就是悲观锁,而乐观锁就认为不会有人来查的,然后当来查的时候,没关门就G了

这里我们可以看到如果老师最近查寝严格,那么悲观锁合适一点,如果不严格,那么乐观锁更加合适一点,因为乐观锁省去了关门的操作,对于进程也就提高了效率

读写锁

读写锁就是把读和写操作区别对待了,如果多个线程进行读,那么不会产生锁冲突,如果多个线程进行写,那么就会产生冲突

在java中

ReentrantReadWriteLock.ReadLock 类表示一个读锁 . 这个对象提供了 lock / unlock 方法进行
加锁解锁 .
ReentrantReadWriteLock.WriteLock 类表示一个写锁 . 这个对象也提供了 lock / unlock 方法进
行加锁解锁
这里面的读加锁和写加锁之间是互斥的,写加锁和写加锁之间是互斥的,读和读之间是不会产生冲突的

重量级锁与轻量级锁

重量级锁里面大量使用了用户态到内核态的转换,这个过程是非常消耗时间的

而轻量级锁里面则避免了非必要的用户态到内核态的转换,从而提高了效率

如果锁冲突严重的时候可以考虑重量级锁,因为很多操作都是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.实现原子类

典型的就是 AtomicInteger . 其中的 getAndIncrement 相当于 i++ 操作
注意:这里的i++是原子的操作,不会产生锁竞争,不会产生脏读的情况

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就会把锁进行自动的粗化,即锁得粒度变大

🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒感谢阅读🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒🔒

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到