10 - 原子类(JUC系列)

目录

一、基本类型原子类

二、数组类型原子类

三、引用类型原子类

四、对象的属性修改原子类

AtomicInteger和AtomicIntegerFieldUpdater有什么区别?


原子类是CAS思想的应用!如果不清楚CAS可查看上一章节。

所谓原子类,前面也说过了,就是指 java.util.concurrent.atomic包下的所有的类:

学习上,都是相通的,没必要挨个去学习。

我们可以将上述的原子类进行再分类:

一、基本类型原子类

常见常用API:

 代码演示

package com.hssy.sqldemo.juc;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {


    public static void main(String[] args) {
        int threadCount = 50;
        MyNumber myNumber = new MyNumber();
        for (int i = 0; i < threadCount; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    myNumber.addPlus();
                }
            }).start();
        }

        // 需要等待一下,让所有线程都执行完成,否则后面获取到的值并不一定是最终值
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}

        System.out.println(myNumber.get());
    }
}
class MyNumber{
    private AtomicInteger atomicInteger = new AtomicInteger();

    public int get() {
        return atomicInteger.get();
    }

    public void addPlus() {
        atomicInteger.getAndIncrement();
    }
}

 

ok~ 

不过这里补充一点额外知识,因为我们要获取最终的值必须等到所有线程任务都执行结束。

否则,获取到的结果就不准确。那么如何判断线程任务是否执行结束呢?

最先想到的可能是像上面这样,给主线程停一段时间,预估任务执行完成后再获取值。

这样其实是不对的,一是必须等到sleep时间过后才能去获取,二是如果出现网络抖动等各种问题,是无法确定线程实际执行时间的。

那么,有什么办法解决了?

使用闭锁CountDownLatch

package com.hssy.sqldemo.juc;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class AtomicIntegerDemo {

    /**
     * 可以使用闭锁
     *
     */
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int threadCount = 50;
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(()->{
                try {
                    for (int j = 0; j < 1000; j++) {
                        myNumber.addPlus();
                    }
                }finally {
                    countDownLatch.countDown();
                }

            }).start();
        }
        countDownLatch.await();
        System.out.println(myNumber.get());
    }
}


class MyNumber{
    private AtomicInteger atomicInteger = new AtomicInteger();

    public int get() {
        return atomicInteger.get();
    }

    public void addPlus() {
        atomicInteger.getAndIncrement();
    }
}

 

通过实际执行,发现只要任务线程任务执行结束,就会给闭锁减少一把,当闭锁减少到0的时候,await()方法将不再阻塞,也就直接输出后续的代码了。

二、数组类型原子类

以AtomicIntegerArray为例: 

 

 

三、引用类型原子类

原子引用类很重要,作用是可以自定义原子类。这在前一章已经讲过了。

我们也使用它AtomicReference写过自旋锁。

也清楚AtomicReference在进行比较并交换的时候,可能会有ABA问题。

通过AtomicStampedReference带有版本号戳记的原子引用类,可以解决ABA问题。

那么,AtomicMarkableReference这个类是干嘛的呢?

其实,它也是一个戳记原子引用类,但是它是状态戳,但是它只能使用一次,因此也叫做一次性戳。

代码演示一下

package com.hssy.sqldemo.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;



/**
 * 结论:
 * AtomicStampedReference 的戳是 版本号戳
 * AtomicMarkableReference 的戳是 状态戳
 * 两者都可以解决ABA问题
 * 前者是通过版本号更新的方式解决ABA问题
 * 后者是是状态只能改一次,也即使用一次,即一次性戳,故不存在ABA问题
 */

public class AtomicDemo {
    public static void main(String[] args) {
        AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100, false);
        new Thread(()->{
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "线程的初始标识:" + marked);
            // 暂停1s 保证线程B拿到同样的初始标识
            try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}

            markableReference.compareAndSet(100,500,marked,!marked);

            // 标志是否可以反复修改,如果可以则修改一定成功
            boolean b = markableReference.compareAndSet(500, 100, !marked, marked);
            System.out.println("标志是否可以反复修改:" + b );
        },"A").start();

        new Thread(()->{
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "线程的初始标识:" + marked);

            // 暂停2s,保证让线程A执行完成
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}

            System.out.println(
                    Thread.currentThread().getName() +
                    "线程是否修改成功:" +
                    markableReference.compareAndSet(100,2000,marked,!marked) +
                    "  修改后的值为: " +
                    markableReference.getReference() +
                    "  当前的标志为:" +
                    markableReference.isMarked()
            );
        },"B").start();
    }
}

四、对象的属性修改原子类

思考:

AtomicIntegerAtomicIntegerFieldUpdater有什么区别?

AtomicInteger和AtomicIntegerFieldUpdater都是的原子类,都能够保证对于一个变量的操作具有原子性,但它们有以下区别:

  • AtomicInteger:是一个原子类,能够保证对于一个整型变量的操作具有原子性。它通过CAS(Compare And Swap)操作实现原子性,即先比较目标值是否与预期值相等,如果相等,则将目标值更新为新值。在多线程环境下,CAS操作能够保证对于一个变量的操作具有原子性,从而避免了线程安全问题。AtomicInteger适用于对于整型变量的操作,例如自增、自减等。
  • AtomicIntegerFieldUpdater:也是一个原子类,它能够保证对于一个类的某个整型字段的操作具有原子性,不需要将整个类变成线程安全的。它通过反射机制获取类的某个字段的偏移量,然后使用CAS操作实现对于该字段的原子性操作。需要注意的是,这个字段必须是volatile类型的,不能是static类型的。AtomicIntegerFieldUpdater适用于需要对于类的某个字段的操作具有原子性的场景。

举个例子,假设有如下代码:

public class Counter {
    private volatile int count;
    private static final AtomicIntegerFieldUpdater<Counter> countUpdater = AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
    
    public void increment() {
        countUpdater.incrementAndGet(this);
    }
    
    public int getCount() {
        return countUpdater.get(this);
    }
}

这个代码中,Counter类中的count字段是volatile类型的,然后通过AtomicIntegerFieldUpdater获取count字段的偏移量,从而对于该字段的操作具有原子性。increment()方法用于对于count字段进行自增操作,getCount()方法用于获取count字段的值。这样,我们就可以在不将整个Counter类变成线程安全的情况下,对于count字段进行线程安全的操作。

另外,值得注意的是,AtomicIntegerFieldUpdater的使用场景并不是很广泛,通常情况下,使用AtomicInteger即可满足需求。AtomicIntegerFieldUpdater的使用场景通常是在需要对于某个字段的操作具有原子性,但是又不想将整个类变成线程安全的情况下使用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

何苏三月

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

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

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

打赏作者

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

抵扣说明:

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

余额充值