锁策略、CAS、synchronized原理-创新互联

1.常见的锁策略

(1)乐观锁 和 悲观锁

在合浦等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供成都做网站、成都网站制作 网站设计制作按需定制网站,公司网站建设,企业网站建设,品牌网站设计,营销型网站建设,成都外贸网站建设公司,合浦网站建设费用合理。

乐观锁:预测锁竞争的情况不激烈(工作量较少)

悲观锁:预测锁竞争的情况很激烈(工作量较多)

(2)轻量级锁 和 重量级锁

轻量级锁:加锁和解锁的开销较小,效率更高。(乐观锁通常是一个轻量级锁)

重量级锁:加锁和解锁的开销较大,效率较低。(悲观锁通常是一个重量级锁)

(3)自旋锁 和 挂起等待锁

自旋锁:是一种典型的轻量级锁,会不断去尝试获取锁。

挂起等待锁:是一种典型的重量级锁,会进入阻塞队列,暂时不参与cpu调度。

(4)互斥锁 和 读写锁

互斥锁:synchronized锁就是一个互斥锁,如果一个线程加锁了,另一个线程要获取锁,会进入阻塞等待。

读写锁:如果多个线程都只进行读操作,那么此时是没有线程问题的,无需加锁。而如果在锁中的代码有读也有写,或者全是写(简单来说就是代码中带有写操作)才会进行加锁。

(5)公平锁 和 非公平锁

公平锁:假如有两个线程一个阻塞等待了1分钟,一个等待10秒钟,此时锁被释放会先给等待时间长的加锁。

非公平锁:仍然是上面的情况,但是此时它们的获取概率相等。

(6)可重入锁 和 不可重入锁

可重入锁:一个线程重复加多次锁,不会死锁。

不可重入锁:一个线程重复加两次锁,会出现死锁。

2.synchronized实现了哪些锁策略

(1)synchronized既是一个乐观锁又是一个悲观锁

默认是乐观锁,如果竞争激烈会转变为悲观锁。

(2)synchronized既是一个轻量级锁又是一个重量级锁

默认是轻量级锁(乐观锁),如果竞争激烈会转变为重量级锁(悲观锁)。

(3)synchronized的轻量级锁是基于自旋锁的方式实现的;synchronized的重量级锁是基于挂起等待锁的方式实现的。

(4)synchronized是互斥锁,不是读写锁

(5)synchronized是非公平锁

(6)synchronized是可重入锁

3.CAS 3.1 CAS的工作原理

CAS全称:Compare and swap ,字面意思就是:比较并交换

它的运行原理是:

假设内存中的元数据V,V的预期值A,需要修改的值B

(1)比较V和A是否相等

(2)如果相等,则将B写入V(交换B和V)

(3)返回操作结果是否成功

3.2 CAS与线程安全

CAS并非是通过一段代码来实现的,它是通过一条cpu指令完成的。

所以CAS操作时原子的,可以在一定程度上回避线程安全问题。

不过CAS并不通用,更多的时候还是会使用synchronized来解决线程安全问题。

3.3 CAS的作用

(1)实现原子类:在Java中有一种类:原子类(Java标准库提供的),原子类是基于CAS实现的,里面有一些方法可以实现数据的自增、自减等操作。

(2)实现自旋锁:自旋锁的实现形式可以用原子类来完成,每次去获取锁时进行一次CAS操作,然后循环去获取即可。

3.4 CAS的典型问题:ABA问题

在CAS运行中会检查value(V)和oldValue(A)的值是否相等,这个时候有两种可能:第一种是没有被改过,第二种是被改过但是被还原回来了。但是CAS在判定时仍然是将其判定为相等。

假如:我去银行的提款机取钱,我的账户上有1000元,我要取500元,但是我不小心嗯了两次,并且两次都受理了,假设它的内部逻辑是通过CAS实现的,那么我第一次取钱判定余额为1000通过成功出了500元,但是在第二次判定之前有人给我转了500元进来,所以第二次判定时仍然是1000元,所以提款机又给我吐了500,但是按照我的想法,CAS不应该执行后面的操作,也就不应该再给我吐出500,此时就出现了问题。

而针对当前的问题,采取的方案就是假如一个版本号。

假设初始版本号是1,每次修改版本号都进行+1,然后进行CAS的时候,就不以数值基准了,而是以版本号为基准,此时版本号没变,那么就一定没有发生变化(因为版本号只能增不能减,无法还原回去)

4.synchronized原理

synchronized的工作原理是当两个线程针对同一个对象加锁时,会产生阻塞等待。

除此之外,synchronized的内部还有一些优化机制,目的是为了让这个锁更加高效好用。

4.1 锁升级/锁膨胀

synchronized在加锁后会进入这样的几种状态。

(1)无锁

此时synchronized没有被加锁

(2)偏向锁

此时有线程对其加锁,但是此时的加锁并非真正的加锁操作,只是在锁对象上打了一个标记,并没有进行实际的加锁操作,此时的偏向锁比起真正的加锁的开销要少了很多,但是这种状态只存在与没有锁竞争的情况下,如果有其他线程对其尝试加锁,那么就会进行真正的加锁操作,反之,如果执行到了末尾也没有其他线程来加锁,此时就不会加锁,直接把标记消除掉就好了。

(3)轻量级锁

当在偏向锁的阶段有其他线程对其进行加锁,也就是发生锁竞争的时候,那么偏向锁就会升级成为轻量级锁,此时会通过自旋锁的方式进行加锁的,但是不断的去获取锁也会占用cpu资源。此时,如果加锁的线程很快就将锁释放掉,那么自然相安无事,此时的自旋也是划算的;但是,如果迟迟拿不到锁,也就是自旋到一定程度时,就会升级成为重量级锁(挂起等待锁)。

(4)挂起等待锁

如果线程进行了重量级锁的加锁,并且发生了锁竞争,此时竞争的线程就会放入到阻塞队列中,暂时不参与cpu的调度,直到锁被释放,这个线程才有机会被调度到,并且有机会获取锁。

注意:锁的升级是不可以倒退的。

4.2 锁消除

锁消除是编译器的一种智能判定,判断当前代码是否真的需要加锁。

如果加锁代码在某个场景中没有线程安全问题,不需要加锁,那么编译器就会把锁给干掉。

比如StringBuffer,它的关键方法都带有synchronized,但是如果我在单线程的环境下去使用它,那么它的加锁操作就会变得毫无意义,此时编译器就会将锁给干掉了。

4.3 锁粗化

通常情况下,我们希望锁的粒度越细越好,也就是加锁的代码越少越好,因为锁的粒度越细,可以被并发的代码就越多,反之就越少。

但是在有些情况下,锁的粒度反而粗一点会更好,如下:

图中被红色圈住的部分是要加锁的代码,没被圈住的部分是可以不加锁的代码,此时可以看到,每次的解锁与加锁之间的间隔非常的小,因为加锁和解锁的操作也是有一定开销的,在一段代码中频繁大量的进行加锁解锁操作,那么并发带来的效益可能还不如频繁加锁的带来消耗多。就好比:

我完成了三个工作,要汇报给自己的领导

打电话--汇报工作1--挂电话 间隔一分钟

打电话--汇报工作2--挂电话 间隔一分钟

打电话--汇报工作3--挂电话

此时领导一定被你烦的不行,为什么不可以一次性汇报完呢?

在这里也是同样的道理,打电话相当于加锁,挂电话相当于解锁,如果将那些小的间隔也一并加入锁中,如下:

此时,将多个加锁操作合并为一个整体,就可以将上述的情况解决了。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


本文名称:锁策略、CAS、synchronized原理-创新互联
本文来源:http://azwzsj.com/article/dhoijj.html