前面学习了基础的多线程知识。今天开始学习JAVA的并发包java.util.concurrent。java并发包包括 java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks包。今天开始学习atomic包下的内容’
概念
java从jdk1.5开始引入了并发包。其中java.util.concurrent.atomic包,方便在无锁的情况下,进行原子操作。原子变量的底层使用的是CPU的原子指令,但是不同的CPU有不同的架构,指令也就不同。所以也有可能需要提供某种内部的锁机制。所以不能保证线程绝对不会堵塞。
Atomic分类
atomic包下面总共有12个类。根据起作用可以分为四类
1.原子更新基本类型
- AtomicBoolean 原子更新布尔类型
- AtomicInteger 原子更新整型
- AtomincLong 原子更新长整型
原子更新整型的常用方法:
- int get() 获取当前值
- void set(int newValue) 设置为给定值。
- int getAndAdd(int delta) 以原子方式将给定值与当前值相加。
- int decrementAndGet() 以原子方式将当前值减 1。
- int incrementAndGet() 以原子方式将当前值加 1。
- void lazySet(int newValue) 最后设置为给定值。
方法可以通过API查找。我们主要是了解,为什么原子类型的操作就是线程安全的?底层又是如何进行原子操作的呢?
对JVM有过一定了解的应该都知道CAS操作。CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做。就是当且仅当内存值与当前值一致,才进行值的更新。否则不更新。这样就保证了atomic包下面的操作的原子行。那它是如何实现的呢?看一看源码:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
}
这是AtomicInteger中的一段源码。我们可以看到其中有一个Unsafe类,一个volatile 修饰的value. 这个value就是先保证了AtomicInteger操作的可见性。然后Unsafe保证对这个value值的操作都是CAS操作。这样就保证了其原子性。Unsafe源码的一部分:
/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
Unsafe中就是通过这样的方式来实现CAS操作。同理AtomicBoolean 和AtomicLong也是这样的方式。但是java中对long类型和double类型的操作是不具有原子性的。这个在今后学习JVM中会详细学习。还有CAS概念。
2.原子更新数组类型
理解了原子更新基本类型,对于其它类型就比较好理解了。
通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:
- AtomicIntegerArray 原子更新整型数组
- AtomicLongArray 原子更新长整型数组
- AtomicReferenceArray 原子更新引用数组
AtomicIntegerArray常用的方法:
- int get(int i) 获取位置 i 的当前值。
- void set(int i, int newValue) 将位置 i 的元素设置为给定值。
- int length() 返回该数组的长度。
- int getAndAdd(int i, int delta) 以原子方式将给定值与索引 i 的元素相加。
- int getAndDecrement(int i) 以原子方式将索引 i 的元素减 1。
- int getAndIncrement(int i) 以原子方式将索引 i 的元素加 1。
- AtomicIntegerArray(int length) 创建给定长度的新 AtomicIntegerArray。
- AtomicIntegerArray(int[] array) 创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有素。
先来看一下AtomicIntegerArray的部分源码:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int shift;
private final int[] array;
public AtomicIntegerArray(int length) {
array = new int[length];
}
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
public final int getAndAdd(int i, int delta) {
long offset = checkedByteOffset(i);
while (true) {
int current = getRaw(offset);
if (compareAndSetRaw(offset, current, current + delta))
return current;
}
}
通过观察源码,其实AtomicIntegerArray类,跟AtomicInteger的原理相似。其自身拥有一个 int[]的数组array。如果我们调用构造器构造的时候没有传入数组,则直接初始化自身数组,否则,对我们的数组进行克隆。也就是说,它操作的都是自身的数组,对我们传入的数组是不会有任何操作。而对数组中的数据的操作也是通过Unsafe类来实现其原子性的操作。
3.原子更新引用类型
原子更新基本类型,每次只能更新一个变量,而当需要更新多个变量,比如一个对象的多个属性时候,我们就必须用到原子更新引用类型类——AtomicReference. 其常用的方法有:
- V get() 获取当前值。
- void set(V newValue) 设置为给定值。