Java并发编程---原子操作CAS
实现并发操作的方法有两种:一种是使用锁(Synchronized和Lock),另外一种是使用原子操作(CAS)
Synchronized基于阻塞的锁机制可能会带来的问题:
a. 被阻塞的线程优先级很高
b.拿到锁的线程一直不释放锁怎么办?
c.大量的竞争消耗cpu,同时带来死锁或者其他安全问题
基于上述问题,提出了CAS原子操作
a.CAS原理:利用现代处理器都支持的CAS指令,循环这个指令,直到成功为止
b.CAS(Compare And Swap) : 指令级别上保证这是一个原子操作
c.与CAS相关的三个运算符:一个内存地址V,一个期望值A,一个新值B
基本思路:如果地址V上的值和期望的值A相等,就给地址V赋新值B,如果不是,不做任何操作。
(在(死循环)循环中不断进行CAS操作)
d.CAS也会带来一定的问题:
1)ABA问题(A–>B–>A):当第一次取内存地址V时,取到了A的值,可当它要执行CAS操作之前,已经有一个线程对A的值改为B后,再 改为A,这时候的值虽然一样,不过内容已经有过更改。(举例:在现实生活中,你放在座子上的以一杯水被同事喝完,又重新倒满一杯放在那里。很明显,它虽然还是那杯满着的水,可性质上却完全不一样了。) 解决方法:可以通过版本号解决 A1—>B2---->A3 (类似于乐观锁,大多是基于数据版本( Version )记录机制实现。)
2)开销问题:CAS操作长期不成功,cpu不断循环。
3)只能保证一个共享变量的原子操作
原子操作类的使用:
JDK中相关原子操作类的使用:
1)更新基本类型:AtomicBoolean, AtomicInteger, AtomicLong
简单的代码示例(AtomicInteger)
相关的方法也比较简单:getAndIncrement() 先取值再加1
incrementAndGet() 先加1再取值
package 原子操作;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author lenovo
*/
public class UseAtomicInt {
static AtomicInteger ai=new AtomicInteger(10);
public static void main(String[] args) {
//先取值再加1
System.out.println(ai.getAndIncrement());
//先加1再取值
System.out.println(ai.incrementAndGet());
System.out.println(ai.get());
}
}
结果展示:
2) 更新数组类:AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
简单代码示例(AtomicIntegerArray)
在进行值的更改时,其实是进行了一个数组的拷贝,并重新赋值,所以原数组的值并没有改变。
package 原子操作;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
* @author lenovo
*/
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));
System.out.println(value[0]);
}
}
结果展示(第一个结果表示改后新数组的值,第二个结果表示的是原数组的值)
3) 更新引用类型:AtomicReference, AtomicMarkableReference, AtomicStampedReference
引用类型的原子操作类可以封装多个数据,已达到多个变量的原子操作
简单代码示例 (AtomicReference) :
UserInfo这个类封装了姓名和年龄这两个变量,进行值改变的时候,也是重新进行拷贝,再进行值的改变。
package 原子操作;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author lenovo
* 类说明:演示引用类型的原子操作类
*/
public class UseAtomicReference {
static AtomicReference<UserInfo> userRef=new AtomicReference<UserInfo>();
public static void main(String[] args) {
UserInfo user=new UserInfo("zs",15);
userRef.set(user);
UserInfo updateUser=new UserInfo("ls",17);
//这是候的更改是在新生成得对象进行变更
userRef.compareAndSet(user,updateUser);
System.out.println(userRef.get().getName());
System.out.println(userRef.get().getAge());
System.out.println(user.getName());
System.out.println(user.getAge());
}
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;
}
}
}
结果展示(前两个值表示改变后引用的值,后两个值表示原来引用的值):
4) 更新字段类:AtomicReferenceFieldUpdater, AtomicIntegerFieldUpdater, AtomicLongFieldUpdater (很少使用)
解决ABA问题的两个类: AtomicMarkableReference 处理版本使用的是boolean (表示有没有动过)
AtomicStampedReference: 表示动过几次(它的构造函数为AtomicStampedReference(V initialRef, int initialStamp ) 第一个参数表示引用类型的值,第二参数表示版本号 )
简单代码示例(AtomicStampedReference具有较强的实用性,表示的是当我们遇到ABA问题时可采用版本号进行分辨后操作)
该代码中有两个线程对带有版本戳的类进行更改,其中一个线程是在版本正确的情况对引用类型的值进行更改(这种情况下是可以更改成功的)另外一个线程是在版本不正确的情况下对引用类型的值进行更改(这种情况下是更改失败的)
compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) 返回的结果值是Boolean表示更改成功或者失败
其中第一个参数表示期待的引用值,第二个表示新的引用值。
第三个参数表示的是期待的版本戳,第四个表示新的版本戳
package 原子操作;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author lenovo
* 演示带版本戳的原子操作类
*/
public class UseAtomicStampedReference {
//AtomicStampedReference(V initialRef, int initialStamp) 传初始值和版本号
static AtomicStampedReference<String> asr=
new AtomicStampedReference<String>("Mark",0);
public static void main(String[] args) throws InterruptedException {
final int oldStamp=asr.getStamp();
final String oldReference=asr.getReference();
System.out.println(oldReference+"======"+oldStamp);
//正确版本号的线程
Thread rightStampThread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+
"当前变量值为:"+oldReference+" 当前版本号为:"+oldStamp+"-"
+asr.compareAndSet(oldReference,oldReference+" Java",
oldStamp,oldStamp+1));
}
});
//错误版本号的线程
Thread errorStampThread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+
"当前变量值为:"+asr.getReference()+" 当前版本号为:"+asr.getStamp()+"-"
+asr.compareAndSet(asr.getReference(),asr.getReference()+" C",
oldStamp,oldStamp+1));
}
});
rightStampThread.start();
rightStampThread.join();
errorStampThread.start();
errorStampThread.join();
System.out.println(asr.getReference()+"======"+asr.getStamp());
}
}
结果展示(第一行的值表示的是原本引用类型的值和版本号 第二行的值表示的是一个在正确版本戳下对引用类型值的改变
第三行的值表示的是在一个错误版本戳下对引用值类型的改变,第四行代表的更改后最终的结果值)