从本课开始学习并发编程的内容。主要介绍并发编程的基础知识、锁、内存模型、线程池、各种并发容器的使用。
原子
CAS
ABA
举个例子:桌上的满满的一杯水,被打翻了,擦干净收拾完后,再倒一杯,在别人看来以为还是之前那杯。线程1是当事人,线程2是别人,共享变量V是这一杯水,线程1和线程2同时拿到共享变量V的初始值A,各自处理;在线程1中把该值更新成B,又更新成A;线程2再来取值时发现还是A,结果又去做它的处理了。这种场景在某些情形下是不正确的,比如某商场推出活动,凡是会员卡剩余金额少于100元的,商场会给充值20元,持续2天,结果因为有的会员卡本来充了一次20,他消费了50,余额又少于100元了,这样会再次充值20元,导致一张会员卡多次充值。
下面用代码演示一个ABA问题
public void aba() {
AtomicInteger abaInt = new AtomicInteger(100);
Thread t1 = new Thread() {
public void run() {
abaInt.compareAndSet(100, 101);
System.out.println(String.format("thread t1, abaInt: %s", abaInt.get()));
abaInt.compareAndSet(101, 100);
System.out.println(String.format("thread t1, abaInt: %s", abaInt.get()));
}
};
Thread t2 = new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean success = abaInt.compareAndSet(100, 101);
System.out.println(String.format("thread t2, abaInt: %s, isSuccess: %s", abaInt.get(), success));
}
};
t1.start();
t2.start();
}
thread t1, abaInt: 101
thread t1, abaInt: 100
thread t2, abaInt: 101, isSuccess: true
在线程t2中比较的值100,其实已经不是之前的值100了,内存地址中的变量已经经历过A -> B -> A的改变了。
ABA问题的根本原因是无法判断变量是否真正被改变过,所以解决方案是给共享变量追加状态,用来记录共享变量是否被修改过。
原子类在java.util.concurrent.atomic
包下,有以下一些类:
AtomicBoolean 原子更新布尔值
AtomicInteger 原子增减数字的值
AtomicLong 原子更新长数字类型的值
AtomicReference 原子更新引用类型的值
AtomicIntegerArray 原子更新数字数组的元素
AtomicLongArray 原子更新长数字数组的元素
AtomicReferenceArray 原子更新引用类型数组的元素
AtomicIntegerFieldUpdater 原子更新类的数字类型字段的值
AtomicLongFieldUpdater 原子更新类的长数字类型字段的值
AtomicReferenceFieldUpdater 原子更新类的引用类型字段的值
AtomicStampedReference 原子更新带版本号的引用类型的值,可以解决ABA问题
原子更新字段的步骤:
newUpdater()
创建更新器,并且指定要更新的类和字段AtomicIntegerFieldUpdater<User> upd = AtomicIntegerFieldUpdater.newUpdater(User.class, 'age');
public volatile
原子类实现原子操作的基础是CAS,把操作交给硬件底层实现的原子性
下面给一个避免ABA问题的代码:
public void noAba() {
AtomicStampedReference<Integer> noAbaInt = new AtomicStampedReference<Integer>(100, 0);
Thread t1 = new Thread() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
noAbaInt.compareAndSet(100, 101, noAbaInt.getStamp(), noAbaInt.getStamp() + 1);
System.out.println(String.format("thread t1, noAbaInt: %s", noAbaInt.getReference()));
noAbaInt.compareAndSet(101, 100, noAbaInt.getStamp(), noAbaInt.getStamp() + 1);
System.out.println(String.format("thread t1, noAbaInt: %s", noAbaInt.getReference()));
}
};
Thread t2 = new Thread() {
public void run() {
int stamp = noAbaInt.getStamp();
System.out.println("before stamp " + stamp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after stamp " + noAbaInt.getStamp());
boolean success = noAbaInt.compareAndSet(100, 101, stamp, stamp + 1);
System.out.println(String.format("thread t2, noAbaInt: %s, isSuccess: %s", noAbaInt.getReference(), success));
}
};
t1.start();
t2.start();
}
before stamp 0
thread t1, noAbaInt: 101
thread t1, noAbaInt: 100
after stamp 2
thread t2, noAbaInt: 100, isSuccess: false
可以看到线程t2更新失败了,由于版本号不是期望的值,所以更新失败
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。