独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁;
创新互联专注为客户提供全方位的互联网综合服务,包含不限于网站制作、网站设计、翁牛特网络推广、微信小程序开发、翁牛特网络营销、翁牛特企业策划、翁牛特品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联为所有大学生创业者提供翁牛特建站搭建服务,24小时服务热线:18982081108,官方网址:www.cdcxhl.com
而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS;
今天我们就来介绍下cas机制;
1、什么是CAS
2、那些地方采用了 CAS 机制
3、synchronized 和 CAS 的区别
4、为什么需要CAS机制
我们经常使用volatile关键字修饰某一个变量,表明这个变量是全局共享的一个变量,同时具有了可见性和有序性。但是却没有原子性。比如说一个常见的操作a++。这个操作其实可以细分成三个步骤:
(1)从内存中读取a
(2)对a进行加1操作
(3)将a的值重新写入内存中
在单线程状态下这个操作没有一点问题,但是在多线程中就会出现各种各样的问题了。因为可能一个线程对a进行了加1操作,还没来得及写入内存,其他的线程就读取了旧值。造成了线程的不安全现象;
Volatile关键字可以保证线程间对于共享变量的可见性可有序性,可以防止CPU的指令重排序(DCL单例),但是无法保证操作的原子性,所以jdk1.5之后引入CAS利用CPU原语保证线程操作的院子性;
CAS操作由处理器提供支持,是一种原语。原语是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。如 Intel 处理器,比较并交换通过指令的 cmpxchg 系列实现;
1、底层依靠Unsafe的CAS操作来保证原子性;
CAS的实现主要在JUC中的atomic包,我们以AtomicInteger类为例:
- /**
- * Atomically adds the given value to the current value.
- *
- * @param delta the value to add
- * @return the previous value
- */
- public final int getAndAdd(int delta) {
- return unsafe.getAndAddInt(this, valueOffset, delta);
- }
- public final int incrementAndGet() {
- for (;;) {
- int current = get();
- int next = current + 1;
- if (compareAndSet(current, next))
- return next;
- }
- }
- private volatile int value;
- public final int get() {
- return value;
- }
代码是一个无限循环,也就是CAS的自旋。循环体当中做了三件事:
获取当前值;
当前值+1,计算出目标值;
进行CAS操作,如果成功则跳出循环(当前值和目标值相等),如果失败则重复上述步骤;
2、Unsafe.class
- public final int getAndAddInt(Object var1, long var2, int var4) {
- int var5;
- do {
- var5 = this.getIntVolatile(var1, var2);
- } while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//native方法
- return var5;
- }
- ********
- public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);//底层c++实现
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);//底层c++实现
3、compareAndSwapInt为native方法,对应底层hotspot虚拟机unsage.cpp
- UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
- UnsafeWrapper("Unsafe_CompareAndSwapInt");
- oop p = JNIHandles::resolve(obj);
- jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
- return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
- UNSAFE_END
- ***
这里可以看到最终使用了Atomic::cmpxchg来保证原子性,可继续跟进代码
4、Atomic::cmpxchg针对不同平台有不同的实现方式
- ***
- // Adding a lock prefix to an instruction on MP machine
- #define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
- ***
- inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
- int mp = os::is_MP();
- __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
- : "=a" (exchange_value)
- : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
- : "cc", "memory");
- return exchange_value;
- }
最重要的指令为 LOCK_IF_MP , MP是指多CPU(multi processors),最终意义为多CPU的情况下需要lock,通过lock的方式来保证原子;
lock解释:
总之:JAVA中我们使用到涉及到CAS操作的底层实现为对应平台虚拟机中的c++代码(lock指令)实现来保证原子性;
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作;
1、ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A, 那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了;
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A;
从Java1.5开始JDK的atomic包里提供了一个类 AtomicStampedReference 来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值;
2、循环时间长开销大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,即自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销;
如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率;
代码层面,破坏掉for死循环,当自旋超过一定时间或者一定次数时,return退出;
使用类似ConcurrentHashMap的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来,能降低CPU消耗,但是治标不治本;
3、只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性;
这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij;
从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作;
线程数较少、等待时间短可以采用自旋锁进行CAS尝试拿锁,较于synchronized高效;
线程数较大、等待时间长,不建议使用自旋锁,占用CPU较高;
CAS可以保证多线程对数据写操作时数据的一致性;
CAS的思想:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false;
文章名称:Java进阶:线程并发之深入理解CAS机制详解
链接分享:http://www.csdahua.cn/qtweb/news27/264727.html
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网