欢迎您的访问
专注于分享最有价值的互联网技术干货

三、原子操作之AtomicInteger

几个T的资料等你来白嫖
双倍快乐
一点要收藏这个宝藏网站防止丢失,资源帮找!!!

原子操作之 AtomicInteger

1. 前言

从本节开始,正式带领大家认识 Java 并发工具类,今天为大家介绍原子操作之 AtomicInteger。此工具位于 java.util.concurrent.atomic 包中。

本节先解释什么是原子操作,接着介绍 AtomicInteger 工具类的最基本用法,有了这些基本认识之后,给出 AtomicInteger 工具最常用的场合说明,然后通过简单的编码实现一个实际案例,让大家有一个理性的认识,最后带领大家熟悉 AtomicInteger 最常用的一些编程方法,进一步加深对 AtomicInteger 工具类的理解。

AtomicInteger 工具类本身使用很简单,重点是对常用编程方法的准确理解。

下面我们正式开始介绍吧。

2. 概念解释

什么是原子操作呢?所谓原子操作,就是一个独立且不可分割的操作。

AtomicInteger 工具类提供了对整数操作的原子封装。为什么要对整数操作进行原子封装呢?

在 java 中,当我们在多线程情况下,对一个整型变量做加减操作时,如果不加任何的多线程并发控制,大概率会出现线程安全问题,也就是说当多线程同时操作一个整型变量的增减时,会出现运算结果错误的问题。AtomicInteger 工具类就是为了简化整型变量的同步处理而诞生的。

大家记住,在多线程并发下,所有不是原子性的操作但需要保证原子性时,都需要进行原子操作处理,否则会出现线程安全问题。

概念已经了解了,那么 AtomicInteger 工具类怎么用呢?别急,最基本的用法请看下面的描述。

3. 基本用法

// 首先创建一个 AtomicInteger 对象
AtomicInteger atomicInteger = new AtomicInteger();
// 在操作之前先赋值,如果不显式赋值则值默认为 0 ,就像 int 型变量使用前做初始化赋值一样。
atomicInteger.set(1000);
// 之后可以调用各种方法进行增减操作
...
// 获取当前值
atomicInteger.get();
// 先获取当前值,之后再对原值加100
atomicInteger.getAndAdd(100)
// 先获取当前值,之后再对原值减1
atomicInteger.getAndDecrement()
...

是不是很简单,AtomicInteger 在我们日常实践中,到底应该应用在哪些场合比较合适呢?下面我们给出最常用的场景说明。

4. 常用场景

AtomicInteger 经常用于多线程操作同一个整型变量时,简化对此变量的线程安全控制的场合。当在研发过程中遇到这些场景时,就可以考虑直接使用 AtomicInteger 工具类辅助实现,完全可以放弃使用 synchronized 关键字做同步控制。

下面我们用 AtomicInteger 工具实现电影院某场次电影票销售的例子。
20210321215725738.jpg

5. 场景案例

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

    // 首先创建一个 AtomicInteger 对象
    // 代表《神龙晶晶兽》电影上午场次当前可售的总票数 10 张
    private static AtomicInteger currentTicketCount = new AtomicInteger(10);
    // 主程序
    public static void main(String[] args) {
        // 定义3个售票窗口
        for(int i=1; i<=3; i++) {
            TicketOffice ticketOffice = new TicketOffice(currentTicketCount, i);
            // 每个售票窗口开始售票
            new Thread(ticketOffice).start();
        }
    }
}

在上面的代码中,先创建了一个 AtomicInteger 对象,然后创建了 3 个售票窗口模拟售票动作 ,接下来每个售票窗口如何动作呢,看下面的代码。

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

/**
 * 模拟售票窗口
 */
public class TicketOffice implements Runnable {
    // 当前可售的总票数
    private AtomicInteger currentTicketCount;
    // 窗口名称(编号)
    private String ticketOfficeNo;
    // 售票窗口构造函数
    public TicketOffice(AtomicInteger currentTicketCount, int ticketOfficeNo) {
        this.currentTicketCount = currentTicketCount;
        this.ticketOfficeNo = "第" + ticketOfficeNo + "售票窗口";
    }
    // 模拟售票逻辑
    public void run() {
        // 模拟不间断的售票工作(生活中有工作时间段控制)
        while (true) {
            // 获取当前可售的总票数,如果没有余票就关闭当前售票窗口结束售票,否则继续售票
            if (currentTicketCount.get() < 1) {
                System.out.println("票已售完," + ticketOfficeNo + "结束售票");
                return;
            }
            // 模拟售票用时
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (Exception e) {}
            // 当总票数减1后不为负数时,出票成功
            int ticketIndex = currentTicketCount.decrementAndGet();
            if (ticketIndex >= 0) {
                System.out.println(ticketOfficeNo + "已出票,还剩" + ticketIndex + "张票");
            }
        }
    }
}

在 TicketOffice 类中,首先通过 get () 获取了当前可售的总票数,在有余票的情况下继续售票。然后随机休眠代替售票过程,最后使用 decrementAndGet () 尝试出票。我们观察一下运行结果。

第3售票窗口已出票,还剩9张票
第1售票窗口已出票,还剩8张票
第2售票窗口已出票,还剩7张票
第1售票窗口已出票,还剩6张票
第3售票窗口已出票,还剩5张票
第3售票窗口已出票,还剩4张票
第2售票窗口已出票,还剩3张票
第1售票窗口已出票,还剩2张票
第3售票窗口已出票,还剩1张票
第2售票窗口已出票,还剩0张票
票已售完,第2售票窗口结束售票
票已售完,第1售票窗口结束售票
票已售完,第3售票窗口结束售票

在这个案例中,因为存在多个售票窗口同时对一场电影进行售票,如果不对可售票数做并发售票控制,很可能会出现多卖出票的尴尬。例子中没有直接使用 synchronized 关键字做同步控制,而是使用 JDK 封装好的 AtomicInteger 原子工具类实现了并发控制整型变量的操作,是不是很方便呢。

至此,大家对 AtomicInteger 已经有了初步的理解,接下来我们继续丰富对 AtomicInteger 工具类的认识。

6. 核心方法介绍

除了上面代码中使用的最基本的 AtomicInteger (int)、AtomicInteger ()、 set () 、get () 和 decrementAndGet () 方法之外,我们还需要掌握其他几组核心方法的使用。下面逐个介绍。

1、getAndAdd (int) 方法与 AddAndGet (int) 方法

第 1 个方法是先获取原值,之后再对原值做增加。注意获取的值是变更之前的值。而第 2 个方法正好相反,是先对原值做增加操作之后再获取更新过的值。

AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.get());            // 0
System.out.println(atomicInteger.getAndAdd(10));    // 0,获取当前值并加10
System.out.println(atomicInteger.get());            // 10
System.out.println(atomicInteger.addAndGet(20));    // 30,当前值先加20再获取
System.out.println(atomicInteger.get());            // 30

2、getAndIncrement () 方法与 incrementAndGet () 方法

第 1 个方法是先获取值,之后再对原值做增 1 操作,注意获取的值是变更之前的值。而第 2 个方法正好相反,是先对原值做增 1 的操作之后再获取更新过的值。

AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.get());  // 0
System.out.println(atomicInteger.getAndIncrement()); // 0,获取当前值并自增1
System.out.println(atomicInteger.get());  // 1
System.out.println(atomicInteger.incrementAndGet()); // 2,当前值先自增1再获取
System.out.println(atomicInteger.get());  // 2

3、compareAndSet(int expect, int update)

原值与 expect 相比较,如果不相等则返回 false 且原有值保持不变,否则返回 true 且原值更新为 update。

AtomicInteger atomicInteger = new AtomicInteger(10);
System.out.println(atomicInteger.get()); // 10
int expect = 12;
int update = 20;
Boolean b =atomicInteger.compareAndSet(expect, update);
System.out.println(b); // 10 不等于 12 不满足期望,所以返回false,且保持原值不变
System.out.println(atomicInteger.get());

7. 小结

本节通过一个简单的例子,介绍了 AtomicInteger 的基本用法,另外对一些核心方法做了简单介绍。在这个包下面,还有很多类似的工具类,也是对基本类型原子操作的封装,如 AtomicBoolean、AtomicLong,用法大同小异,希望大家在日常研发中多比较多总结,早日掌握之。

赞(0) 打赏
版权归原创作者所有,任何形式转载请联系我们:大白菜博客 » 三、原子操作之AtomicInteger

评论 抢沙发

2 + 6 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏