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