CAS原子操作
原子操作
所谓原子操作是指不会被线程调度机制打断的操作,当某次操作一旦开始,就一直运行到结束,中间不会有任何中断。
举个例子:
A想要从自己的帐户中转1000块钱到B的帐户里。那个从A开始转帐,到转帐结束的这一个过程,称之为一个事务。在这个事务里,要做如下操作:
- 从A的帐户中减去1000块钱。如果A的帐户原来有3000块钱,现在就变成2000块钱了。
- 在B的帐户里加1000块钱。如果B的帐户如果原来有2000块钱,现在则变成3000块钱了。
如果在A的帐户已经减去了1000块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时B的帐户里还没有增加1000块钱。那么,我们称这个操作失败了,要进行回滚。回滚就是回到事务开始之前的状态,也就是回到A的帐户还没减1000块的状态,B的帐户的原来的状态。此时A的帐户仍然有3000块,B的帐户仍然有2000块。
我们把这种要么一起成功(A帐户成功减少1000,同时B帐户成功增加1000),要么一起失败(A帐户回到原来状态,B帐户也回到原来状态)的操作叫原子性操作。
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性。
CAS
Compare And Set(或Compare And Swap),CAS是解决多线程并行情况下使用锁造成性能损耗的一种机制,采用这种无锁的原子操作可以实现线程安全,避免加锁的笨重性。
CAS操作包含三个操作数:内存位置(V)、预期原值(A)、新值(B)。
如果内存位置的值与预期原值相等,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
在java中可以通过循环CAS的方式来实现原子操作。
CAS是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,循环这个指令,直到成功为止。
java提供的CAS原子操作类AtomicInteger等,核心就是CAS(CompareAndSwap)。
注意:原子操作和锁是一样的一种可以保证线程安全的方式,如何让线程安全就看如何使用锁或者如何使用原子操作CAS使用了正确的原子操作,所以保证了线程安全。
原子操作类
当高并发的情况下,对于基本数据类型或者引用数据类型的操作,可能会产生线程安全问题,为了避免多线程问题的处理方式一般有加锁,但是加锁会影响性能,所以这个时候可以考虑使用原子操作类。CAS由于是在硬件方面保证的原子性,不会锁住当前线程,所以执行效率是很高的。
常见的原子操作类:
实战
1,多线程累加一个较大的数值,比较原子操作类和加锁各自的耗时。
public class AtomicIntegerDemo {
static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
// test();
Synch synch = new Synch();
synch.start();
synch.join();
atomic();
// System.out.println(add());
}
//使用加锁累加
static class Synch extends Thread {
@Override
public void run() {
//使用原子操作类统计
long startTime = System.currentTimeMillis();
int count = 1;
// synchronized (this) {
// for (int i = 0; i < 1000000000; i++) {
// count++;
// }
// }
for (int i = 0; i < 1000000000; i++) {
synchronized (this) {
count++;
}
}
long endTime = System.currentTimeMillis();
System.out.println("使用锁累加花费的时间:" + (endTime - startTime) + "......count = " + count);
}
}
//使用原子操作类统计
private static void atomic() {
long startTime = System.currentTimeMillis();
AtomicInteger atomicInteger = new AtomicInteger(1);
for (int i = 0; i < 1000000000; i++) {
atomicInteger.incrementAndGet();
}
long endTime = System.currentTimeMillis();
System.out.println("原子操作类累加花费的时间:" + (endTime - startTime) + "......count = " + atomicInteger.get());
}
//测试基本用法
private static void test() {
AtomicInteger num = new AtomicInteger(1);
//++i
System.out.println(num.incrementAndGet());
//i++
System.out.println(num.getAndIncrement());
//CAS
System.out.println(num.compareAndSet(3, 4));
System.out.println(num.get());
}
//实现自定义的原子递增方法
private static int add() {
for (; ; ) {
int i = atomicInteger.get();
boolean b = atomicInteger.compareAndSet(i, i + 1);
if (b) {
return atomicInteger.get();
}
}
}
}
运行结果:
2,使用AtomicInteger类的get方法和compareAndSet方法实现它的递增方法。
public class HalfAtomicInt {
private AtomicInteger atomicI = new AtomicInteger(0);
public void increament() {
for (;;) {
int i = atomicI.get();
boolean suc = atomicI.compareAndSet(i, ++i);
if (suc) {
break;
}
}
}
public int getCount() {
return atomicI.get();
}
}
总结
好处:保证了数据的原子性,避免线程安全问题,替代加锁的性能消耗。
坏处:
1,ABA问题(并发1在修改数据时,虽然还是A,但已经不是初始条件的A了,中间发生了A变B,B又变A的变化,此A已经非彼A,数据却成功修改,可能导致错误,这就是CAS引发的所谓的ABA问题。 可以使用乐观锁的方式解决。)
2,循环时间长开销大(自旋)
3,只能保证一个共享变量的原子操作(可以使用AtomicRefrence原子操作类将多个变量合并成一个对象来解决)
解决ABA问题:
AtomicMarkableReference:内部是一个boolean类型的版本号,可以记录是否被更改过。
AtomicStampedReference:内部是一个int类型的版本号,可以记录被更改的次数。
例如:使用AtomicStampedReference,避免ABA问题,查看内部是int类型的版本号。
public class AtomicStampedReferenceDemo {
public static void main(String[] args) throws InterruptedException {
//设置初始化版本号是0
AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference("a1", 0);
//初始的值和版本号
String reference = atomicStampedReference.getReference();
int stamp = atomicStampedReference.getStamp();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("目前的值:" + reference + "............版本号:" + stamp
+ ",修改结果:" + atomicStampedReference.compareAndSet(reference, "a2", stamp, stamp + 1));
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
String reference = atomicStampedReference.getReference();
System.out.println("目前的值:" + reference + "............版本号:" + atomicStampedReference.getStamp()
+ ",修改结果:" + atomicStampedReference.compareAndSet(reference, "a2", stamp, stamp + 1));
}
});
thread1.start();
thread1.join();
thread2.start();
thread2.join();
}
}
运行结果: