java.util.concurrent.atomic包中并发原子类AtomicInteger讲解

一、简要介绍

  1. 今天在项目代码中,注意到有使用AtomicInteger类,这个类主要是在java.util.concurrent.atomic并发包下的。
  2. Java并发机制的三个特性,如下所示:
    (1)原子性
    (2)可见性
    (3)有序性
  3. volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证可见性、有序性。但是无法保证原子性;今天我们所讲述的AtomicInteger的作用就是为了保证原子性。
    代码如下所示:
package concurrent;

public class Concurrent {
    private static volatile int a = 0;

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        //定义5个线程,每个线程加10
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(a++);
                        Thread.sleep(1000);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
    }
}

在上述示例代码中,我们定义了一个变量a。并且使用了5个线程分别去增加。为了保证可见性和有序性,我们定义了一个静态的volatile的关键字来对a进行修饰。在这里我们只测试原子性。如果我们第一次接触的话肯定会觉得5个线程,每个线程加10,最后结果一定是50呀。但是当对代码运行后,结果却跟我们想象的不太一样。
在这里插入图片描述
我们不难发现,运行的结果除了含有重复值外,最大的值也不是50,而仅仅只有39。这个现象的出现,让我不禁感叹:为什么会出现这个问题呢?这是因为变量a,虽然保证了可见性和有序性,但是却没有保证原子性。
分析如下所示:
其实代码中对于a++的操作,大致可以分为3个步骤:
(1)从主存中读取a的值
(2)对a进行加1操作
(3)把a重新刷新到主存

这三个步骤在单线程中一点问题都没有,但是到了多线程就出现了问题了。比如说有的线程已经把a进行了加1操作,但是还没来得及重新刷入到主存,其他的线程就重新读取了旧值。因为才造成了错误。如何去解决呢?方法当然很多,但是为了和我们今天的主题对应上,很自然的联想到使用AtomicInteger。
下面我们使用AtomicInteger重新来测试一遍:

package concurrent;

import java.util.concurrent.atomic.AtomicInteger;

public class Concurrent_1 {
    //使用AtomicInteger定义a
    static AtomicInteger atomicInteger = new AtomicInteger();

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        //定义5个线程,每个线程加10
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        //incrementAndGet
                        System.out.println(atomicInteger.incrementAndGet());
                        Thread.sleep(500);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
    }
}

结果如下所示:
在这里插入图片描述
当我们使用AtomicInteger类,无论你执行多少次,最后的结果一定是50。这是为什么呢?一切都跟AtomicInteger类的底层实现方法有关系,具体分析详见下日描述。

[补充] compareAndSwapInt又叫做CAS。

CAS 即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值