java.util.concurrent.atomic 包详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java.util.concurrent.atomic 包详解相关的知识,希望对你有一定的参考价值。

Atomic包的作用:

方便程序员在多线程环境下,无锁的进行原子操作

Atomic包核心:

Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作

关于CAS

compare and swap,比较和替换技术,将预期值与当前变量的值比较(compare),如果相等则使用新值替换(swap)当前变量,否则不作操作;

现代CPU已广泛支持CAS指令,如果不支持,那么JVM将使用自旋锁,与互斥锁一样,两者都需先获取锁才能访问共享资源,但互斥锁会导致线程进入睡眠,而自旋锁会一直循环等待直到获取锁;

另外,有一点需要注意的是CAS操作中的ABA问题,即将预期值与当前变量的值比较的时候,即使相等也不能保证变量没有被修改过,因为变量可能由A变成B再变回A,解决该问题,可以给变量增加一个版本号,每次修改变量时版本号自增,比较的时候,同时比较变量的值和版本号即可

Atomic包主要提供四种原子更新方式

  • 原子方式更新基本类型
  • 原子方式更新数组
  • 原子方式更新引用
  • 原子方式更新字段

原子方式更新基本类型

以下三个类是以原子方式更新基本类型

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。

以AtomicInteger为例:

package cn.com.example.concurrent.atomic;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicIntegerTest extends Thread {

    private AtomicInteger atomicInteger;

    public AtomicIntegerTest(AtomicInteger atomicInteger) {
        this.atomicInteger = atomicInteger;
    }

    @Override
    public void run() {
        int i = atomicInteger.incrementAndGet();
        System.out.println("generated  out number:" + i);
    }

    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger();
        for (int i = 0; i < 10; i++) {//10个线程
            new AtomicIntegerTest(counter).start();
        }
    }
}

输出:

generated  out number:1
generated  out number:2
generated  out number:3
generated  out number:4
generated  out number:5
generated  out number:6
generated  out number:7
generated  out number:8
generated  out number:9
generated  out number:10

注意:Atomic包提供了三种基本类型的原子更新,剩余的Java的基本类型还有char,float和double等,其更新方式可以参考AtomicBoolean的思路来现,AtomicBoolean是把boolean转成整型再调用compareAndSwapInt进行CAS来实现的,类似的short和byte也可以转成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits转成整形和长整形进行相应处理

原子方式更新数组

以下三个类是以原子方式更新数组

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素

以AtomicIntegerArray为例,其方法与AtomicInteger很像,多了个数组下标索引

package cn.com.example.concurrent.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicIntegerArrayTest {
    private static int threadCount = 1000;
    private static CountDownLatch countDown = new CountDownLatch(threadCount);
    static int[] values = new int[10];
    static AtomicIntegerArray ai = new AtomicIntegerArray(values);

    private static class Counter implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                for (int j = 0; j < 10; j++) {//所有元素+1
                    ai.getAndIncrement(j);
                }
            }
            countDown.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[threadCount];
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(new Counter());
        }
        for (int i = 0; i < threadCount; i++) {
            threads[i].start();
        }
        countDown.await();
        for (int i = 0; i < 10; i++) {
            System.out.println(ai.get(i) + " ");
        }
        System.out.println();
        for (int i = 0; i < 10; i++) {
            System.out.println(values[i] + " ");
        }
    }
}

输出:

100000 
100000 
100000 
100000 
100000 
100000 
100000 
100000 
100000 
100000 

0 
0 
0 
0 
0 
0 
0 
0 
0 
0 

需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。

原子方式更新引用

以下三个类是以原子方式更新引用,与其它不同的是,更新引用可以更新多个变量,而不是一个变量

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。

以AtomicReference为例

package cn.com.example.concurrent.atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicReferenceTest {
    public static void main(String[] args) {

        // 创建两个Person对象,它们的id分别是101和102。
        Person p1 = new Person(101);
        Person p2 = new Person(102);
        // 新建AtomicReference对象,初始化它的值为p1对象
        AtomicReference ar = new AtomicReference(p1);
        // 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
        ar.compareAndSet(p1, p2);

        Person p3 = (Person) ar.get();
        System.out.println("p3 is " + p3);
        System.out.println("p3.equals(p1)=" + p3.equals(p1));
    }
}

class Person {
    volatile long id;

    public Person(long id) {
        this.id = id;
    }

    public String toString() {
        return "id:" + id;
    }
}

输出:

p3 is id:102
p3.equals(p1)=false

新建AtomicReference对象ar时,将它初始化为p1。
紧接着,通过CAS函数对它进行设置。如果ar的值为p1的话,则将其设置为p2。
最后,获取ar对应的对象,并打印结果。p3.equals(p1)的结果为false,这是因为Person并没有覆盖equals()方法,而是采用继承自Object.java的equals()方法;而Object.java中的equals()实际上是调用"=="去比较两个对象,即比较两个对象的地址是否相等。

原子方式更新字段

以下三个类是以原子方式更新字段

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型,用于解决使用CAS进行原子更新时,可能出现的ABA问题。

以AtomicIntegerFieldUpdater为例

package cn.com.example.concurrent.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * Created by Jack on 2017/1/7.
 */
public class AtomicIntegerFieldUpdaterTest {

    // 创建原子更新器,并设置需要更新的对象类和对象的属性
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");

    public static void main(String[] args) throws InterruptedException {
        // 设置柯南的年龄是10岁
        User conan = new User("conan", 10);
        // 柯南长了一岁,但是仍然会输出旧的年龄
        System.out.println(a.getAndIncrement(conan));
        // 输出柯南现在的年龄
        System.out.println(a.get(conan));
    }

    public static class User {
        private String name;
        public volatile int old;

        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }

        public String getName() {
            return name;
        }

        public int getOld() {
            return old;
        }
    }
}

输出:

10
11

注意: old 需要声明为 volatile



以上是关于java.util.concurrent.atomic 包详解的主要内容,如果未能解决你的问题,请参考以下文章