多线程的知识还真是不少,之前的文章只是说完了关于锁的一系列实现,回观整个juc包,至少还包括下面几个方面:
并发容器(各种线程安全的集合类);
并发框架(Fork/Join);
原子操作类(各种Atomic类);
并发工具类(3个经典类);
线程池(Excutor);
慢慢总结吧,这篇文章先说一下原子操作类。
atomic包
从JDK5开始,java提供了java.util.concurrent.atomic包,其中的原子操作类提供了一种用法简单、性能高效、线程安全的更新变量的方式。
在JDK7中,java.util.concurrent.atomic包提供了如下原子操作类:
基本类型
AtomicBoolean
AtomicInteger
AtomicLong
数组类型
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray<E>
引用类型
AtomicReference<V>
AtomicReferenceFieldUpdater<T,V>
AtomicMarkableReference<V>
更新字段
AtomicIntegerFieldUpdater<T>
AtomicLongFieldUpdater<T>
AtomicStampedReference<V>
原子更新基本类型类
包括AtomicBoolean、AtomicInteger和AtomicLong 。顾名思义,就是对布尔型、整型和长整型的原子更新。其中实现基本一致,下面以AtomicInteger为例介绍下常用API:
int addAndGet(int data)
以原子方式将数据的数值和AtomicInteger实例中的数值相加并返回结果;
boolean compareAndSet(int expect , int update)
如果输入的数值等于预期值,则将该值设置为输入的值
int getAndIncrement()
原子方法+1,这里返回的是自增前的值
int getAndDecrement()
原子方法-1,这里返回的是自增前的值
void lazySet(int newValue)
最终设置为newValue,但是有一小段时间其他线程可能读到旧值
int getAndSet(int newValue)
以原子方式设置为newValue的值,并返回旧值
注意,上面带有get和and字眼的的接口有把get和and后的操作互换的接口,逻辑基本一致,区别在于返回的是原子操作前的值还是操作后的值。
观其源码,上面接口的实现都是通过循环判断+unSafe的CAS操作来实现的,以addAndGet为例:
public final long addAndGet(long delta) {
for (;;) {
long current = get();
long next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
同理,我们也可以自行使用unSafe提供的3个CAS方法(compareAndSwapObject、compareAndSwapInt和compareAndSwapLong)来实现对其他原子类型的原子操作。
原子更新数据类型
包括AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray<E>。顾名思义就是对整型数组、长整型数组和引用类型数组的原子操作。
主要接口同上面原子操作类的接口名基本一致,只是多了一个索引的参数,以AtomicIntegerArray为例:
int addAndGet(int i, int delta)
boolean compareAndSet(int i, int expect, int update)
int getAndIncrement(int i)
int getAndDecrement(int i)
void lazySet(int i, int newValue)
int getAndSet(int i, int newValue)
其中i就是数组索引。观其源码实现也是在循环里判断并执行CAS操作,多了一步就是先把值安全索引从数组中取出来而已,以addAndSet为例:
public final int addAndGet(int i, int delta) {
long offset = checkedByteOffset(i);
while (true) {
int current = getRaw(offset);
int next = current + delta;
if (compareAndSetRaw(offset, current, next))
return next;
}
}
原子更新引用类型
上面都是去原子更新单个变量的操作,如果我们想原子更新多个变量,那么我们就需要用到这个原子更新引用类型的类,包括:AtomicReference<V>
、AtomicReferenceFieldUpdater<T,V>和AtomicMarkableReference<V> 。其中AtomicReference是用于原子更新引用类型,AtomicReferenceFieldUpdater用于原子更新引用类型中的字段,AtomicMarkableReference用于原子更新带有标记为的引用类型(构造方法为AtomicMarkableReference(V
initialRef, boolean initialMark))。
其提供出的接口也基本为get和set两种,这里不赘述。底层实现就是使用了unSafe类的compareAndSwapObject接口来完成的,比较简单。
原子更新字段类型
如需原子更新某个类中的某个字段,那么就可以用到源自更新字段类型。包括AtomicIntegerFieldUpdater<T>、
AtomicLongFieldUpdater<T>和AtomicStampedReference<V> 。前两个顾名思义,就是原子更新整型和长整型字段的更新器。AtomicStampedReference是支持原子更新带有版本号的引用类型,该类将整数值与引用关系关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
如果想使用原子更新字段类,那么首先要保证2点:一是先通过newUpdater接口创建出一个原子更新字段类实例(因为原子更新字段类都是抽象类);二是要更新类的字段必须用volatile修饰符修饰。
下面是一个AtomicIntegerFieldUpdater的例子:
public static class Container {
public volatile int num;
public Container(int num){
this.num = num;
}
public int getNum(){
return num;
}
}
public static AtomicIntegerFieldUpdater<Container> ar = AtomicIntegerFieldUpdater.newUpdater(Container.class, "num");
public static void main(String[] args) {
Container c = new Container(12);
ar.getAndIncrement(c);
System.out.println(c.getNum());
}