深入理解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)題。