前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底理解Java并发:Java并发原子类

彻底理解Java并发:Java并发原子类

作者头像
栗筝i
发布2022-12-01 21:40:49
5290
发布2022-12-01 21:40:49
举报
文章被收录于专栏:迁移内容迁移内容

本篇内容包括:原子类概述、原子类分类(Atomic 基本类型原子类、Array 数组类型原子类、Atomic\Reference 引用类型原子类、Atomic\FieldUpdater 原子更新属性、Adder 加法器、Accumulator 积累器)、原子类 Demo 等内容!

一、原子类概述

我们把一个或者多个操作在 CPU 执行的过程中不能被中断的特性称之为原子性。

在 Jdk1.5 开始 Java 开始引进提供了 java.util.concurrent.atomic 包,到 Jdk8 时,atomic 包共提供了 16 个原子类,分为 6 种类型,分别是:①、基本类型原子类;②、数组类型原子类;③、引用类型原子类;④、原子更新属性;⑤、Adder 加法器;⑥、积累器。

当多线程更新变量的值时,可能得不到预期的值,当然增加 syncronized 关键字可以解决线程并发的问题。但原子类提供了一种用法简单,性能高效,线程安全的更新变量的方式。原子类基本都是使用 Unsafe 实现的包装类,主要用到了 Unsafe 的系统层面的 CAS 实现。

原子类相较于 synchronized 关键字和 lock,有着以下的优点:

  1. 简单:操作简单,底层实现简单
  2. 高效:占用资源少,操作速度快
  3. 安全:在高并发和多线程环境下要保证数据的正确性

对于是需要简单的递增或者递减的需求场景,使用 synchronized 关键字和 lock 固然可以实现,但代码写的会略显冗余,且性能会有影响,此时用原子类更加方便。


二、原子类分类

atomic 包共提供了 16 个原子类,分为 6 种类型:

1、Atomic(基本类型原子类)

Atomic 基本类型原子类,包括三种:AtomicInteger、AtomicLong 和 AtomicBoolean。

AtomicInteger、 AtomicLong、 AtomicBoolean 提供对 int、long、boolean 的原子性操作,这 3 个类提供的方法几乎一模一样。以 AtomicInteger 为例,它包含如下常用的方法:getAndAdd() 返回旧值;addAndGet() 返回新值;getAndIncrement() 加1;incrementAndGet()compareAndSet() 原子替换值等。

对于其他基本类型的变量,如 char、float、double,可以先转换为整型,然后再进行原子操作。例如,AtomicBoolean 就是先把 Boolean 转换成整型,再使用 compareAndSwaplnt 进行 CAS 操作。

2、Array(数组类型原子类)

Array 数组类型原子类,包括三种:AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray。

AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 提供对 int、long、boolean 的数组元素的原子性操作。原子替换数组中的元素:求i个元素的偏移量,提高位移运算,提高性能。

3、Atomic\Reference(引用类型原子类)

Atomic\Reference 引用类型原子类,包括三种:AtomicReference、AtomicStampedReference 和 AtomicMarkableReference。

AtomicReference 提供了对 对象类型的原子性操作。

AtomicStampedReference 和 AtomicMarkableReference 以版本戳的方式解决原子类型的 ABA 问题,其中 AtomicStampedReference 是原子更新带有标记位(整数)的引用类型;AtomicMarkableReference 是原子更新带有标记位(布尔)的引用类型。

4、Atomic\FieldUpdater(原子更新属性)

Atomic\FieldUpdater 原子更新属性,包括三种:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater。

Atomic\FieldUpdater 原子更新属性,提供对指定对象的指定字段进行原子性操作

如果一个类是自己编写的,则可以在编写的时候把成员变量定义为 Atomic 类型。但如果是一个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,就需要使用 AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater 三个类,将要使用的传给这个类,让其去做原子更新操作。

5、Adder(加法器)

Adder 加法器,包括两种:LongAdder 和 DoubleAdder。

Atomic 基本类型,可以保证多线程下的线程安全。但是,在并发量很大的场景下,Atomic 基本类型原子类(AtomicInteger 和 AtomicLong)有很大的性能问题。LongAdder 和 DoubleAdder 就是 Atomic 基本类型原子类的升级类型,专门用于数据统计,性能更高!

6、Accumulator(积累器)

Accumulator 积累器,包括两种:LongAccumulator 和 DoubleAccumulator。

Accumulator 和 Adder 非常相似,实际上 Accumulator 就是一个更通用版本的 Adder,比如 LongAccumulator 是 LongAdder 的功能增强版,因为 LongAdder 的 API 只有对数值的加减,而 LongAccumulator 提供了自定义的函数操作。


三、原子类Demo

这里以基本类型原子类中的 AtomicInteger 类为例,介绍通用的 API 接口和使用方法。

首先是几个常用的API:

代码语言:javascript
复制
// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回更新的值)。
int addAndGet(int delta)

// 以原子方式将给定值与当前值相加,可用于线程中的计数使用,(返回以前的值)
int getAndAdd(int delta)

// 以原子方式将当前值加 1(返回更新的值)
int incrementAndGet()

// 以原子方式将当前值加 1(返回以前的值)
int getAndIncrement() 

// 以原子方式设置为给定值(返回旧值)
int getAndSet(int newValue)

// 以原子方式将当前值减 1(返回更新的值)
int decrementAndGet() :

// 以原子方式将当前值减 1(返回以前的值)
int getAndDecrement()

// 获取当前值
get()

这里定义一个临界变量 val,起 10 个异步线程,每个线程都是对这个临界变量进行 10000 次自增操作,如下:

代码语言:javascript
复制
public class AtomicWrongDemo {
    private int val = 0;

    public static void main(String[] args) {
        // 初始化实例
        AtomicWrongDemo atomicWrongDemo = new AtomicWrongDemo();
        for (int i = 0; i < 10; ++i) {
            new Thread(atomicWrongDemo::increase).start();
        }
        // 让主线程休眠3秒,保证前面起的10个异步线程都执行完毕
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicWrongDemo.getVal());
    }

    private void increase() {
        for (int i = 0; i < 20000; ++i) {
            ++this.val;
        }
        Thread t = Thread.currentThread();
        System.out.println("线程:" + t.getName() + "已经执行完毕,当前 val 结果为" + this.val);
    }

    private int getVal() {
        return this.val;
    }
}

运行结果会我们期望的 100000 少很多(操作数越大,距期望值相差越多),比如我这里结果为 39757,出现比 100000 少很多的结果,是因为自增操作 ++i 不是原子操作,出现了竞争,需要对临界变量做同步处理。

使用 synchronized 关键字和 lock 固然可以实现,但这里只是对临界变量 val++ 时做同步处理,有种高射炮打蚊子的感觉,且加锁后势必会对性能有所印象,这种场景正是我们使用 Atomic 类的场景,如下:

代码语言:javascript
复制
public class AtomicDemo {
    private AtomicInteger val = new AtomicInteger();

    public static void main(String[] args) {
        // 初始化实例
        AtomicDemo atomicDemo = new AtomicDemo();

        for (int i = 0; i < 10; ++i) {
            new Thread(atomicDemo::increase).start();
        }

        // 让主线程休眠3秒,保证前面起的10个异步线程都执行完毕
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicDemo.getVal().toString());
    }

    private void increase() {
        for (int i = 0; i < 10000; ++i) {
            this.val.incrementAndGet();
        }
    }

    private AtomicInteger getVal() {
        return this.val;
    }
}

这里我们使用了 AtomicInterger 类的 increamentAndGet 方法,以原子方式将当前值加 1(返回更新的值),结果自然是每次运行都打印 100000,可以看到代码写起来很简洁,很轻量级。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、原子类概述
  • 二、原子类分类
    • 1、Atomic(基本类型原子类)
      • 2、Array(数组类型原子类)
        • 3、Atomic\Reference(引用类型原子类)
          • 4、Atomic\FieldUpdater(原子更新属性)
            • 5、Adder(加法器)
              • 6、Accumulator(积累器)
              • 三、原子类Demo
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档