深入理解JUC:第三章:AtomicReference原子引用
第一章講解了volatile不保證原子性,為解決原子性使用了AtomicInteger原子整型,解決了基本類(lèi)型運(yùn)算操作的原子性的問(wèn)題,那我們自定義的實(shí)體類(lèi)或者基本數(shù)據(jù)類(lèi)型都要保證原子性呢?使用AtomicReference原子引用
AtomicInteger原子整型:
package com.javaliao.backstage;
import java.util.concurrent.atomic.AtomicInteger;
class MyData{
volatile int number = 0;
AtomicInteger atomicInteger = new AtomicInteger();
public void changeData(){
atomicInteger.getAndIncrement();//加一
}
}
/**
* 線(xiàn)程對(duì)變量的讀取賦值要先將變量從主內(nèi)存拷貝自己的工作內(nèi)存空間,在工作內(nèi)存中進(jìn)行操作,操作完成后再將變量寫(xiě)回主內(nèi)存
*/
public class Demo {
//主線(xiàn)程main,程序入口
public static void main(String[] args) {
//創(chuàng)建對(duì)象,number在主內(nèi)存為0
MyData myData = new MyData();
for (int i = 1; i <= 20; i++) {
//創(chuàng)建20個(gè)線(xiàn)程
new Thread(()->{
//一個(gè)線(xiàn)程執(zhí)行1000次加一的操作
for (int j = 1; j <= 1000; j++) {
myData.changeData();
}
},String.valueOf(i)).start();
}
//程序不關(guān)閉會(huì)繼續(xù)執(zhí)行main線(xiàn)程和GC線(xiàn)程,判斷線(xiàn)程數(shù)量大于二繼續(xù)執(zhí)行上面的代碼,
while (Thread.activeCount() > 2){
Thread.yield();
}
//理想中number的數(shù)量為20*1000=20000,而volatile不保證原子性,實(shí)際情況一般打印number的數(shù)量不是20000
System.out.println(Thread.currentThread().getName()+"\t 打印number的數(shù)量:" + myData.atomicInteger);
}
}
AtomicReference原子引用直接上代碼:
package com.javaliao.backstage;
import java.util.concurrent.atomic.AtomicReference;
class User{
String userName;
int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Demo {
//主線(xiàn)程main,程序入口
public static void main(String[] args) {
User user1 = new User("java_wxid",25);
User user2 = new User("javaliao",22);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
System.out.println(atomicReference.compareAndSet(user1, user2)+"\t"+atomicReference.get().toString());
new Thread(()->{
System.out.println(atomicReference.compareAndSet(user1, user1)+"\t"+atomicReference.get().toString());
},"a").start();
}
}
控制臺(tái):
但是這不能解決上一章講解的CAS的ABA問(wèn)題
ABA問(wèn)題代碼:
public class Demo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
new Thread(()->{
//執(zhí)行ABA操作
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
try {
//暫停一秒,保證t1線(xiàn)程完成了一次ABA操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019));
System.out.println(atomicReference.get());
},"t2").start();
}
}
上一章講了這中間有貓膩,所以提供解決方案:
使用AtomicStampedReference版本號(hào)原子引用
只要T1的版本號(hào)弱于T2的線(xiàn)程版本號(hào)就需要更新,假設(shè)線(xiàn)程T1的第二個(gè)版本號(hào)的值為2019,而線(xiàn)程T2已經(jīng)修改了二次了,版本號(hào)為3,那此時(shí)就不能那線(xiàn)程T2的版本號(hào)為2的進(jìn)行比較并交換,需要重新將線(xiàn)程T3的版本號(hào)的值拷貝更新再進(jìn)行操作。
package com.javaliao.backstage;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Demo {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("===============解決ABA問(wèn)題方案===============");
new Thread(()->{
//獲取版本號(hào)
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"第一次版本號(hào):"+stamp+"\t
當(dāng)前實(shí)際最新值:"+atomicStampedReference.getReference());
try {
//暫停一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t
第二次版本號(hào):"+atomicStampedReference.getStamp()+"\t
當(dāng)前實(shí)際最新值:"+atomicStampedReference.getReference());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t
第三次版本號(hào):"+atomicStampedReference.getStamp()+"\t
當(dāng)前實(shí)際最新值:"+atomicStampedReference.getReference());
},"t3").start();
new Thread(()->{
//獲取版本號(hào)
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第一次版本號(hào):"+stamp);
try {
//暫停一秒,保證t3線(xiàn)程完成了一次ABA操作
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t
最新版本號(hào):"+atomicStampedReference.getStamp()+"\t 當(dāng)前t4的版本號(hào)是:"+stamp);
System.out.println(Thread.currentThread().getName()+"\t 只有最新的版本號(hào)和t4的版本號(hào)一致時(shí),才可以寫(xiě)回主內(nèi)存,是否寫(xiě)回成功:"+
atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1));
System.out.println(Thread.currentThread().getName()+"\t 當(dāng)前實(shí)際最新值:"+atomicStampedReference.getReference());
},"t4").start();
}
}
控制臺(tái):
這個(gè)時(shí)候就可以讓t4線(xiàn)程去更新版本號(hào)為3的值100,解決了CAS只管結(jié)果不管過(guò)程的問(wèn)題。