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操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

经验分享 程序员 微信小程序 职场和发展