JUC学习之路(引用类型原子操作类)(四)

7 篇文章 0 订阅
5 篇文章 0 订阅

目录

AtomicReference引用原子类

         实现具体的引用数据存储

AtomicStampedReference 引用原子类(带有版本号的引用处理)

         实现带有版本的引用类来进行原子操作

AtomicMarkableReference 引用原子类 (带标记的引用处理)

         使用原子标记性的操作类

        ABA 实例分析 


        引用类型在程序的开发之中也是需要进行同步处理的,例如:在一个多线程的操作类中,需要引用其他类型的对象,这个时候就要进行引用的原子类操作的使用,但是对于引用类型的原子类在J.U.C.A 包里面实际上提供有三种类型:AtomicReference(引用类型原子类)、AtomicStampedReference(带有引用版本号的原子类)、AtomicMarkableReference(带有标记的原子类型)

AtomicReference引用原子类

这个类可以直接实现引用数据类型的存储,在进行修改的时候可以实现线程安全的更新操作,更新的实现原理还是CAS。

         实现具体的引用数据存储

class BookReperence{
    private String title;   // 书名
    private double price;   // 价格

    public BookReperence(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "【图书】 名称 = "+this.title+"、价格 = "+this.price;
    }
}
public class AtomicReferenceTest {

    @Test
    public void referenceTest() throws Exception {
        // 1、定义图书
        BookReperence book = new BookReperence("JUC 学习之路",69.8);
        // 2、原子引用
        AtomicReference<BookReperence> atomicReference = new AtomicReference<>(book);
        // 3、数据更改
        atomicReference.compareAndSet(book,new BookReperence("JAVA 学习之路",69.8));
        System.out.println(atomicReference);
    }
}

        按照Java的基本概念来讲的话,引用数据类型和基本数据类型是完全不同的,毕竟存在了堆栈管理,而基本数据类型是直接以数值的形式存储的,实际上对于CAS 的操作是存在一个局限性的。

AtomicStampedReference 引用原子类(带有版本号的引用处理)

        在之前的AtomicReference类之中是直接滚局保存的对象的地址数值来进行CAS处理,而这个类是在数值之上又增加了一个版本编号。如果在更改的时候,版本编号相同,最终才允许修改,如果不同则不允许修改。

         实现带有版本的引用类来进行原子操作

class BookStamped{
    private String title;   // 书名
    private double price;   // 价格

    public BookStamped(String title, double price) {
        this.title = title;
        this.price = price;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "【图书】 名称 = "+this.title+"、价格 = "+this.price;
    }
}

public class AtomicStampedReferenceTest {

    @Test
    public void referenceTest() throws Exception {
        // 1、定义图书
        BookStamped book1 = new BookStamped("JUC 学习之路",69.8);
        BookStamped book2 = new BookStamped("JAVA 学习之路",69.8);
        // 2、 1 是一个版本,正常的设计来讲,每次修改之后的版本编号一般都需要增加1
        AtomicStampedReference<BookStamped> stampedReference = new AtomicStampedReference<>(book1,1);
        // 3、 现在保存的版本号是 1 ,我们现在匹配 5,版本不匹配,无法修改内容
        System.out.println(
                "【 × 引用版本修改】" + stampedReference.compareAndSet(book1, book2, 3, 5));
        System.out.println(
                "【 × 原子引用数据】版本戳:" + stampedReference.getStamp() + "、存储内容:" + stampedReference.getReference());
        // 4、测试正确操作
        System.out.println(
                "【 √ 引用版本修改】" + stampedReference.compareAndSet(book1, book2, 1, 2));
        System.out.println(
                "【 √ 原子引用数据】版本戳:" + stampedReference.getStamp() + "、存储内容:" + stampedReference.getReference());
    }
}

        此时与之前的引用类型相比,加入了一个版本戳的支持,在进行替换之前一般都要进行版本戳的匹配,版本戳匹配成功之后才可以进行更新,但是千万要记住,一般写这种操作的程序往往都需要在每次获取之后,对版本号做一个更新,如果不做更新的话,实际上会存在一些同步的安全隐患问题。

AtomicMarkableReference 引用原子类 (带标记的引用处理)

        这种原子类也属于带有版本标记的原子引用类型,但是与版本戳的操作相比,它只有两个版本类型:true、false,所有称之为 标记性的原子操作类。

         使用原子标记性的操作类

class BookMarkable {
    private String title;   // 书名
    private double price;   // 价格

    public BookMarkable(String title, double price) {
        this.title = title;
        this.price = price;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "【图书】 名称 = " + this.title + "、价格 = " + this.price;
    }
}
public class AtomicMarkableReferenceTest {

    @Test
    public void referenceTest() throws Exception {
        // 1、定义图书
        BookStamped book1 = new BookStamped("JUC 学习之路", 69.8);
        BookStamped book2 = new BookStamped("JAVA 学习之路", 69.8);
        // 2、 true 是一个标记
        AtomicMarkableReference<BookStamped> markableReference = new AtomicMarkableReference<>(book1, true);
        // 3、 现在保存的版本号是 true
        System.out.println(
                "【 × 引用版本修改】" + markableReference.compareAndSet(book1, book2, false, true));
        System.out.println(
                "【 × 原子引用数据】版本戳:" + markableReference.isMarked() + "、存储内容:" + markableReference.getReference());
        // 4、测试正确操作
        System.out.println(
                "【 √ 引用版本修改】" + markableReference.compareAndSet(book1, book2, true, false));
        System.out.println(
                "【 √ 原子引用数据】版本戳:" + markableReference.isMarked() + "、存储内容:" + markableReference.getReference());
    }
}

        实际上不管是版本戳还是标记的原子引用类,最终都是为了去解决项目之中可能出现的 ABA 数据的错乱问题,就相当于是两个线程,彼此同时对一个资源进行操作,但是A 的操作有可能被B 的操作所覆盖。

        ABA 实例分析 

        对于ABA 问题最简单的理解就是:现在A和B 两位开发工程师同时打开了一个相同的程序文件,但是A 在打开之后由于有其他的事情要忙,所以暂时没有做任何的代码编写,而 B 却一直在进行代码编写,当 B 把代码写完并保存后关上了电脑离开了,而 A 处理完其他事情后发现没有什么可写的,于是就直接保存退出了,这样 B 的修改就消息不见了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倪家李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值