Java中的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 concurrency; import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { //相当于i++,返回的是旧值,看方法名就知道,先获取再自增 System.out.println(ai.getAndIncrement()); System.out.println(ai.get()); //先自增,再获取 System.out.println(ai.incrementAndGet()); System.out.println(ai.get()); //增加一个指定值,先add,再get System.out.println(ai.addAndGet(5)); System.out.println(ai.get()); //增加一个指定值,先get,再set System.out.println(ai.getAndSet(5)); System.out.println(ai.get()); } }
注意:Atomic包提供了三种基本类型的原子更新,剩余的Java的基本类型还有char,float和double等,其更新方式可以参考AtomicBoolean的思路来现,AtomicBoolean是把boolean转成整型再调用compareAndSwapInt进行CAS来实现的,类似的short和byte也可以转成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits转成整形和长整形进行相应处理;
原子方式更新数组
以下三个类是以原子方式更新数组,
- AtomicIntegerArray:原子更新整型数组里的元素。
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素。
以AtomicIntegerArray为例,其方法与AtomicInteger很像,多了个数组下标索引;
package concurrency; import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicIntegerArrayTest { static int[] valueArr = new int[] { 1, 2 }; //AtomicIntegerArray内部会拷贝一份数组 static AtomicIntegerArray ai = new AtomicIntegerArray(valueArr); public static void main(String[] args) { ai.getAndSet(0, 3); //不会修改原始数组value System.out.println(ai.get(0)); System.out.println(valueArr[0]); } }
原子方式更新引用
以下三个类是以原子方式更新引用,与其它不同的是,更新引用可以更新多个变量,而不是一个变量;
- AtomicReference:原子更新引用类型。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。
以AtomicReference为例,
package concurrency; import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceTest { public static AtomicReference<User> atomicUserRef = new AtomicReference<User>(); public static void main(String[] args) { User user = new User("conan", 15); atomicUserRef.set(user); User updateUser = new User("Shinichi", 17); atomicUserRef.compareAndSet(user, updateUser); System.out.println(atomicUserRef.get().getName()); System.out.println(atomicUserRef.get().getOld()); } static class User { private String name; private int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }
原子方式更新字段
以下三个类是以原子方式更新字段,
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型,用于解决使用CAS进行原子更新时,可能出现的ABA问题。
以AtomicIntegerFieldUpdater为例,
package concurrency; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterTest { private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater .newUpdater(User.class, "old"); public static void main(String[] args) { 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; //注意需要用volatile修饰 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; } } }
参考资料
《JAVA并发编程实战》