原子操作CAS
- 今天给大家介绍原子类操作,在多线程同时更新一个变量,可能造成更新后的值不准确。synchronized会保证多线程不会同时更新一个变量,但性能相对而言较低。在Jdk1.5增加了java.util.concurrent包,这个包中的原子操作类就为我们提供了一种性能高效、线程安全地更新一个变量的方式。 看看jdk中对原子操作的定义:
现在的操作系统都支持原子操作,其原理就是利用了现代处理器都支持的CAS的指令,循环这个指令,直到成功为止。
同样使用原子操作也会导致下面几个问题:
- ABA 问题: 值由A变成B,又变回A。可以通过版本号解决
- 开销问题:CAS操作长期不成功,cpu不断的循环
- 只能保证一个共享变量的原子操作
jdk提供的原子类
更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
下面通过几个例子的常用方法介绍我们的原子类:
1.AtomicInteger
public class UseAtomicInt {
static AtomicInteger ai = new AtomicInteger(10);
public static void main(String[] args) {
System.out.println(ai.getAndIncrement());//10(out)--->11(先获取值在自增1)
System.out.println(ai.incrementAndGet());//11--->12(out)(先增1在拿值)
System.out.println(ai.get()); //12
}
}
2.AtomicIntegerArray
public class AtomicArray {
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
//更改的值仅仅是原子类的值,引用对象的值不变
public static void main(String[] args) {
ai.getAndSet(0, 3);// 更新对应下标下的值
System.out.println(ai.get(0));// 3,原子类中下标为0的值,已经被修改为3
System.out.println(value[0]);//1, 对象数组的值是没有变动的
}
}
3.AtomicReference
public class UseAtomicReference {
static AtomicReference<UserInfo> userRef = new AtomicReference<UserInfo>();
//更改的值仅仅是原子类的值,引用对象的值不变
public static void main(String[] args) {
UserInfo user = new UserInfo("Mark", 16);//要修改的实体的实例
userRef.set(user);
UserInfo updateUser = new UserInfo("nick", 17);//要变化的新实例
userRef.compareAndSet(user, updateUser);
System.out.println(userRef.get().getName());//输出:nick
System.out.println(userRef.get().getAge());//输出:17
System.out.println(user.getName()) //输出:Mark
System.out.println(user.getAge()); //输出:16
}
//定义一个实体类
static class UserInfo {
private String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
}
注:ABA问题可以由AtomicMarkableReference和AtomicStampedReference解决,AtomicMarkableReference中可以有判断没有动过boolean ,AtomicStampedReference可以通过版本号发现修改过几次。以AtomicStampedReference为例:
public class UseAtomicStampedReference {
static AtomicStampedReference<String> asr =
new AtomicStampedReference<>("Mark",0) ; //初始化版本号为0
public static void main(String[] args) throws InterruptedException {
final int oldStamp = asr.getStamp();//最初的版本号
final String oldReferenc = asr.getReference();
System.out.println(oldReferenc+"==========="+oldStamp);
//执行成功的线程
Thread rightStampThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+"当前变量值:"+oldReferenc+"当前版本戳:"+oldStamp+"-"
+asr.compareAndSet(oldReferenc, oldReferenc+"Java",
oldStamp, oldStamp+1)); //版本号自增1
}
});
//执行失败的线程
Thread errorStampThread = new Thread(new Runnable() {
@Override
public void run() {
String reference = asr.getReference();
System.out.println(Thread.currentThread().getName()
+"当前变量值:"+reference+"当前版本戳:"+asr.getStamp()+"-"
+asr.compareAndSet(reference, reference+"C",
oldStamp, oldStamp+1));
}
});
rightStampThread.start();
rightStampThread.join();
errorStampThread.start();
errorStampThread.join();
System.out.println(asr.getReference()+"==========="+asr.getStamp());
}
}