做网站运作国珍,销售课程视频免费,wordpress新界面,网站绝对布局转载自 乐观锁的一种实现方式——CAS在深入理解乐观锁与悲观锁一文中我们介绍过锁。本文在这篇文章的基础上#xff0c;深入分析一下乐观锁的实现机制#xff0c;介绍什么是CAS、CAS的应用以及CAS存在的问题等。线程安全
众所周知#xff0c;Java是多线程的。但是#xff0…转载自 乐观锁的一种实现方式——CAS在深入理解乐观锁与悲观锁一文中我们介绍过锁。本文在这篇文章的基础上深入分析一下乐观锁的实现机制介绍什么是CAS、CAS的应用以及CAS存在的问题等。线程安全
众所周知Java是多线程的。但是Java对多线程的支持其实是一把双刃剑。一旦涉及到多个线程操作共享资源的情况时处理不好就可能产生线程安全问题。线程安全性可能是非常复杂的在没有充足的同步的情况下多个线程中的操作执行顺序是不可预测的。
Java里面进行多线程通信的主要方式就是共享内存的方式共享内存主要的关注点有两个可见性和有序性。加上复合操作的原子性我们可以认为Java的线程安全性问题主要关注点有3个可见性、有序性和原子性。
Java内存模型JMM解决了可见性和有序性的问题而锁解决了原子性的问题。这里不再详细介绍JMM及锁的其他相关知识。但是我们要讨论一个问题那就是锁到底是不是有利无弊的锁存在的问题
Java在JDK1.5之前都是靠synchronized关键字保证同步的这种通过使用一致的锁定协议来协调对共享状态的访问可以确保无论哪个线程持有共享变量的锁都采用独占的方式来访问这些变量。独占锁其实就是一种悲观锁所以可以说synchronized是悲观锁。
悲观锁机制存在以下问题
在多线程竞争下加锁、释放锁会导致比较多的上下文切换和调度延时引起性能问题。一个线程持有锁会导致其它所有需要此锁的线程挂起。如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置引起性能风险。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是每次不加锁而是假设没有冲突而去完成某项操作如果因为冲突失败就重试直到成功为止。
与锁相比volatile变量是一个更轻量级的同步机制因为在使用这些变量时不会发生上下文切换和线程调度等操作但是volatile不能解决原子性问题因此当一个变量依赖旧值时就不能使用volatile变量。因此对于同步最终还是要回到锁机制上来。乐观锁
乐观锁 Optimistic Locking其实是一种思想。相对悲观锁而言乐观锁假设认为数据一般情况下不会造成冲突所以在数据进行提交更新的时候才会正式对数据的冲突与否进行检测如果发现冲突了则让返回用户错误的信息让用户决定如何去做。
上面提到的乐观锁的概念中其实已经阐述了他的具体实现细节主要就是两个步骤冲突检测和数据更新。其实现方式有一种比较典型的就是Compare and Swap(CAS)。CAS
CAS是项乐观锁技术当多个线程尝试使用CAS同时更新同一个变量时只有其中一个线程能更新变量的值而其它线程都失败失败的线程并不会被挂起而是被告知这次竞争中失败并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置V、预期原值A和新值(B)。如果内存位置的值与预期原值相匹配那么处理器会自动将该位置值更新为新值。否则处理器不做任何操作。无论哪种情况它都会在 CAS 指令之前返回该位置的值。在 CAS 的一些特殊情况下将仅返回 CAS 是否成功而不提取当前值。CAS 有效地说明了“我认为位置 V 应该包含值 A如果包含该值则将 B 放到这个位置否则不要更改该位置只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查数据更新的原理是一样的。
这里再强调一下乐观锁是一种思想。CAS是这种思想的一种实现方式。
Java对CAS的支持
在JDK1.5 中新增java.util.concurrent(J.U.C)就是建立在CAS之上的。相对于对于synchronized这种阻塞算法CAS是非阻塞算法的一种常见实现。所以J.U.C在性能上有了很大的提升。
我们以java.util.concurrent中的AtomicInteger为例看一下在不使用锁的情况下是如何保证线程安全的。主要理解getAndIncrement方法该方法的作用相当于 i 操作。
public class AtomicInteger extends Number implements java.io.Serializable { private volatile int value; public final int get() { return value; } public final int getAndIncrement() { for (;;) { int current get(); int next current 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } }
在没有锁的机制下需要字段value要借助volatile原语保证线程间的数据是可见的。这样在获取变量的值的时候才能直接读取。然后来看看i是怎么做到的。
getAndIncrement采用了CAS操作每次从内存中读取数据然后将此数据和1后的结果进行CAS操作如果成功就返回结果否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。ABA问题
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据而在下时刻比较并替换那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A这时候另一个线程two也从内存中取出A并且two进行了一些操作变成了B然后two又将V位置的数据变成A这时候线程one进行CAS操作发现内存中仍然是A然后one操作成功。尽管线程one的CAS操作成功但是不代表这个过程就是没有问题的。
部分乐观锁的实现是通过版本号version的方式来解决ABA问题乐观锁每次在执行数据的修改操作时都会带上一个版本号一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行1操作否则就执行失败。因为每次操作的版本号都会随之增加所以不会出现ABA问题因为版本号只会增加不会减少。总结
Java中的线程安全问题至关重要要想保证线程安全就需要锁机制。锁机制包含两种乐观锁与悲观锁。悲观锁是独占锁阻塞锁。乐观锁是非独占锁非阻塞锁。有一种乐观锁的实现方式就是CAS 这种算法在JDK 1.5中引入的java.util.concurrent中有广泛应用。但是值得注意的是这种算法会存在ABA问题。CAS与对象创建
另外CAS还有一个应用那就是在JVM创建对象的过程中。对象创建在虚拟机中是非常频繁的。即使是仅仅修改一个指针所指向的位置在并发情况下也不是线程安全的可能正在给对象A分配内存空间指针还没来得及修改对象B又同时使用了原来的指针来分配内存的情况。解决这个问题的方案有两种其中一种就是采用CAS配上失败重试的方式保证更新操作的原子性。