深刻理解JUC:第三章:AtomicReference原子引用

第一章讲解了volatile不保证原子性,为解决原子性使用了AtomicInteger原子整型,解决了基本类型运算操做的原子性的问题,那咱们自定义的实体类或者基本数据类型都要保证原子性呢?使用AtomicReference原子引用java

AtomicInteger原子整型:ide

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();//加一
    }
}

/**
 * 线程对变量的读取赋值要先将变量从主内存拷贝本身的工做内存空间,在工做内存中进行操做,操做完成后再将变量写回主内存
 */
public class Demo {

    //主线程main,程序入口
    public static void main(String[] args) {
        //建立对象,number在主内存为0
        MyData myData = new MyData();

        for (int i = 1; i <= 20; i++) {
            //建立20个线程
            new Thread(()->{
                //一个线程执行1000次加一的操做
                for (int j = 1; j <= 1000; j++) {
                    myData.changeData();
                }
            },String.valueOf(i)).start();
        }


        //程序不关闭会继续执行main线程和GC线程,判断线程数量大于二继续执行上面的代码,
        while (Thread.activeCount() > 2){
           Thread.yield();
        }
        //理想中number的数量为20*1000=20000,而volatile不保证原子性,实际状况通常打印number的数量不是20000
        System.out.println(Thread.currentThread().getName()+"\t 打印number的数量:" + myData.atomicInteger);
    }

}

AtomicReference原子引用直接上代码:this

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 {
    //主线程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();
    }
}

控制台:atom

可是这不能解决上一章讲解的CAS的ABA问题.net

ABA问题代码:线程

public class Demo {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    public static void main(String[] args) {
        new Thread(()->{
            //执行ABA操做
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();
        new Thread(()->{
            try {
                //暂停一秒,保证t1线程完成了一次ABA操做
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 2019));
            System.out.println(atomicReference.get());
        },"t2").start();
    }
}

 上一章讲了这中间有猫腻,因此提供解决方案:3d

使用AtomicStampedReference版本号原子引用code

只要T1的版本号弱于T2的线程版本号就须要更新,假设线程T1的第二个版本号的值为2019,而线程T2已经修改了二次了,版本号为3,那此时就不能那线程T2的版本号为2的进行比较并交换,须要从新将线程T3的版本号的值拷贝更新再进行操做。对象

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问题方案===============");
        new Thread(()->{
            //获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"第一次版本号:"+stamp+"\t 当前实际最新值:"+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 第二次版本号:"+atomicStampedReference.getStamp()+"\t 当前实际最新值:"+atomicStampedReference.getReference());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第三次版本号:"+atomicStampedReference.getStamp()+"\t 当前实际最新值:"+atomicStampedReference.getReference());
        },"t3").start();
        new Thread(()->{
            //获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+stamp);
            try {
                //暂停一秒,保证t3线程完成了一次ABA操做
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"\t 最新版本号:"+atomicStampedReference.getStamp()+"\t 当前t4的版本号是:"+stamp);
            System.out.println(Thread.currentThread().getName()+"\t 只有最新的版本号和t4的版本号一致时,才能够写回主内存,是否写回成功:"+
                    atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1));
            System.out.println(Thread.currentThread().getName()+"\t 当前实际最新值:"+atomicStampedReference.getReference());
        },"t4").start();
    }
}

控制台:blog

这个时候就能够让t4线程去更新版本号为3的值100,解决了CAS只管结果无论过程的问题。