CAS : Compare and swap ,是原子操作的一种。 CAS操作包含三个值,分别是 V(内存位置),A(预期原值),(B)新值,在进行CAS 操作是,首选知道到变量所在的内存位置,对这个变量进行判断,如果这个值和预期值相等,那么就将新值B替换掉原值A,放入内存V中。
对于CAS操作来说,它保证了,并发概念的中 原子性,可见性,有序性中的原子性。对于Java来讲,JVM的内存模型可以保证有序性。java中的关键字volatile
关键字可以保证可见性。所以对于 volatile修饰的AtomicXXX
变量的操作基本上都可以认为是线程安全的。通过阅读AtomicInteger的源码可以看出其维护的原始变量value是通过volatile修饰的所以,对于AtomicXX变量的操作是线程安全的!
private volatile int value;
另外,对于原子操作,并不能简单的简单的认为一条语句即为一个原子操作,对于这点熟悉汇编的可能会了解的多一些。比如对于下面的几个操作
1. i++ // 1. 从内存中读出 i值 2. 对其进行 +1操作 3. 将值赋值到i所在的内存中
2. i = j // 1.从内存中取出j值,3.将 j 的值赋值到 i所在的内存
3. i += 10 // 同 1
4. i = 10 // 这个操作是原子性的,只有一步,将 常亮赋值到 i所在的内存中
使用Atomic变量比加锁效率更高!,这是因为对Atomic 变量进行操作时没有阻塞线程,而使用同步代码块时会阻塞线程,并且会造成程序的复杂性增高!
public class AtomicTester {
//private AtomicInteger value;
private Integer value;
private long start;
public void test() {
//value = new AtomicInteger(1);
start = System.currentTimeMillis();
value = 0;
for (int count = 0;count < 4 ;count++){
new Thread(() -> {
synchronized (value) {
while (true) {
value++;
isGetThere();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
public void isGetThere() {
// if (value.get() == 1000){
if (value.equals(1000)){
System.out.println("use time " + (System.currentTimeMillis() - start));
System.exit(0);
}
}
}
利用以上代码进行测试(拿掉注释进行比较),通过比较发现,利用syn代码 块时耗时 35 秒左右,利用 原子变量耗时 20秒左右。当线程数量增加,性能差距更大
ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。 从Java1.5开始JDK的 atomic包里提供了一个类AtomicStampedReference 来解决ABA问题。这个类的 compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
对于非原始变量,可以使用AtomicReference
进行操作,虽然可执行的操有限。基本也只限于CAS。但是如果对于多线程访问的变量的操作不是很复杂,可以考虑这种方式
网友评论