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

简介: 深入理解JUC:第三章:AtomicReference原子引用

第一章讲解了volatile不保证原子性,为解决原子性使用了AtomicInteger原子整型,解决了基本类型运算操作的原子性的问题,那我们自定义的实体类或者基本数据类型都要保证原子性呢?使用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();//加一
    }
}
/**
 * 线程对变量的读取赋值要先将变量从主内存拷贝自己的工作内存空间,在工作内存中进行操作,操作完成后再将变量写回主内存
 */
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原子引用直接上代码:

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

控制台:

但是这不能解决上一章讲解的CAS的ABA问题

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


上一章讲了这中间有猫腻,所以提供解决方案:

使用AtomicStampedReference版本号原子引用

只要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();
    }
}

控制台:


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

相关文章
|
5月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
80 1
|
6月前
|
存储 安全 Java
Java面试题:深入探索Java内存模型,Java内存模型中的主内存与工作内存的概念,Java内存模型中的happens-before关系,volatile关键字在Java内存模型中的作用
Java面试题:深入探索Java内存模型,Java内存模型中的主内存与工作内存的概念,Java内存模型中的happens-before关系,volatile关键字在Java内存模型中的作用
46 1
|
6月前
|
安全 Oracle Java
(四)深入理解Java并发编程之无锁CAS机制、魔法类Unsafe、原子包Atomic
其实在我们上一篇文章阐述Java并发编程中synchronized关键字原理的时候我们曾多次谈到过CAS这个概念,那么它究竟是什么?
134 1
|
6月前
|
存储 安全 Java
Java面试题:Java内存模型中的主内存与工作内存是如何协同工作的?请解释Java内存模型中的可见性、原子性和有序性,举例说明Java内存模型中的happens-before关系
Java面试题:Java内存模型中的主内存与工作内存是如何协同工作的?请解释Java内存模型中的可见性、原子性和有序性,举例说明Java内存模型中的happens-before关系
53 0
|
8月前
|
监控 安全 Java
Java并发编程学习5-对象的组合
本篇介绍一些线程安全的组合模式(包含实例封闭,Java监视器模式,线程安全性委托,客户端加锁机制)
78 1
Java并发编程学习5-对象的组合
|
安全 Java 编译器
Java并发编程学习3-可见性和对象发布
本篇介绍对象的共享之可见性和对象发布
93 2
Java并发编程学习3-可见性和对象发布
|
设计模式 安全 Java
JUC第十二讲:JUC锁 - 看不懂锁核心类 AQS 原理来打我
JUC第十二讲:JUC锁 - 看不懂锁核心类 AQS 原理来打我
|
存储 缓存 安全
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )(三)
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )(三)
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )(二)
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )(二)
|
缓存 安全 Java
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )
《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )

热门文章

最新文章