接下来,我们继续学习Java多线程之进阶篇。Java多线程之进阶篇(一)介绍了线程池的相关内容,让我们大致对线程池有了初步的整体的认识,但是具体到稍微深入的知识点还是有点懵,这一篇主要介绍JUC的原子类。
在Java SE5 中java.util.concurrent.atomic包下提供了一系列支持无锁线程安全修改操作的基础变量。这些原子类是对volatile机制的扩展,并提供下面形式的原子性条件更新操作:
boolean compareAndSet(expectedValue, updateValue);
每个Atomic类基本都有这个方法,compareAndSet通过原子实现CAS操作,最底层基本基于汇编语言实现。
CAS是compareAndSet的一个简称:
(1)以知当前内存里面的值current和预期要传的值(expectedValue)进行比较(==)
(2)如果比价(==)相等,返回true,更新值为updateValue;如果不相等,返回false,值不改变。
我们来看看atomic包下的原子类有哪些,下面是JDK 1.7.0_17中源码的截图:
在atomic包下有12个类,这些类大致可以划分为四种类型:
(1)基本原子类:AtomicInteger,AtomicLong,AtomicBoolean这3个基本类型的原子类
(2)数组原子类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray这3个原子类
(3)引用原子类:AtomicReference,AtomicStampedRerence,,AtomicMarkableReference这3个原子类
(4)对象属性修改原子类:AtomicIntegerFiledUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater这3个原子类
下面对这四种原子类进行逐一介绍:
下面来看一个简单的示例:
public class AtomicTest {
/**
* @param args
*/
public static void main(String[] args) {
AtomicBoolean atomicBoolean = new AtomicBoolean();
System.out.println("atomicBoolean默认值为:"+atomicBoolean.get());
atomicBoolean.set(true);
System.out.println("atomicBoolean set设置值为:"+atomicBoolean.get());
//以原子方式设置为给定值,并返回以前的值
Boolean oldValue = atomicBoolean.getAndSet(true);
System.out.println("getAndSet返回以前的值为:"+oldValue);
System.out.println("atomicBoolean现在的值为:"+atomicBoolean.get());
boolean expectedValue = false;
boolean newValue = true;
boolean value = atomicBoolean.compareAndSet(expectedValue, newValue);
System.out.println("compareAndSet后返回的值:"+value);
System.out.println("atomicBoolean现在的值为:"+atomicBoolean.get());
}
}
输出结果为:
atomicBoolean默认值为:false
atomicBoolean set设置值为:true
getAndSet返回以前的值为:true
atomicBoolean现在的值为:true
compareAndSet后返回的值:false
atomicBoolean现在的值为:true
主要对compareAndSet()方法进行简单说明介绍:
compareAndSet()方法允许你对AtomicBoolean的当前值与一个期望值进行比较,如果当前值等于期望值的话,将会对AtomicBoolean设置一个新值(即,第二个参数)。如果不等于期望值的,AtomicBoolean将返回期望的值(即,第一个参数),AtomicBoolean的值还是原来的值。注意
:AtomicBoolean的返回值和AtomicBoolean得到的值。
AtomicLong和AtomicInteger的用法基本一致,只是数据的基本类型不一样,下面以AtomicInteger为例,来说明用法:
以简单的例子来看看用法:
public class AtomicIntegerTest {
/**
* @param args
*/
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(123);
System.out.println(integer);
// 以原子方式设置当前值时
integer.set(147);
int expect = 147;
int update = 300;
// 如果当前值 == expect,则以原子方式将该值设置为update。成功返回true,否则返回false,并且不修改原值。
integer.compareAndSet(expect, update);
System.out.println(integer);
//以原子方式将当前值加1,并返回加1前的值。等价于“num++”
System.out.println("getAndIncrement的值:"+integer.getAndIncrement());
//以原子方式将当前值加1,并返回加1后的值。等价于“++num”
System.out.println("incrementAndGet的值:"+integer.incrementAndGet());
//以原子方式将当前值减1,并返回减1前的值。等价于“num--”
System.out.println("getAndDecrement的值为:"+integer.getAndDecrement());
//以原子方式将当前值减1,并返回减1后的值。等价于“--num”
System.out.println("decrementAndGet的值为:"+integer.decrementAndGet());
//以原子方式将当前值加10,并返回加10后的值
System.out.println("addAndGet的值为:"+integer.addAndGet(10));
//以原子方式将当前值加10,并返回加10前的值
System.out.println("getAndAdd的值为:"+integer.getAndAdd(10));
}
}
AtomicReference提供了一个可以被原子性读和写的对象引用变量。原子性的意思是多个想要改变同一个AtomicReference的线程不会导致AtomicReference处于不一致的状态。
AtomicReference函数列表:
// 使用 null 初始值创建新的 AtomicReference。
AtomicReference()
// 使用给定的初始值创建新的 AtomicReference。
AtomicReference(V initialValue)
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean compareAndSet(V expect, V update)
// 获取当前值。
V get()
// 以原子方式设置为给定值,并返回旧值。
V getAndSet(V newValue)
// 最终设置为给定值。
void lazySet(V newValue)
// 设置为给定值。
void set(V newValue)
// 返回当前值的字符串表示形式。
String toString()
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean weakCompareAndSet(V expect, V update)
简单示例:
public class AtomicReferenceTest {
public static void main(String[] args) {
Student stu1 = new Student(123);
Student stu2 = new Student(456);
Student stu3 = new Student(789);
AtomicReference reference = new AtomicReference(stu1);
reference.compareAndSet(stu2, stu3);
System.out.println(reference.get());
//创建泛型AtomicReference
AtomicReference refer = new AtomicReference("atmoicReference about generic");
String str = refer.get();
System.out.println(str);
}
}
结果输出:
id:123
atmoicReference about generic
AtomicReference的源码比较简单,我就不粘贴出来了,但是要说明两点:
(1)通过将value设置为volatile类型。这保证了线程修改value的值时,其他线程看到的value值都是最新的value值,即修改之后的volatile的值。
(2)通过CAS设置value。这保证了:当某线程通过CAS函数(compareAndSet函数)设置value时,它的操作是原子的,即线程在操作value时不会别中断。
在前面介绍了AtomicInteger和Atomiclong的操作,但是在这两个类在CAS操作的时候会遇到ABA问题:简单来将就是多线程环境下,2次读写中一个线程修改A->B,然后又B->A,另一个线程看到的值未改变,又继续修改成自己的期望值。当然如果我们不关心过程,只关心结果,那么这个就是无所谓的ABA问题。为了解决ABA问题,Java为我们提供了AtomicStampedReference和AtomicMarkableReference类,两者的区别是:AtomicStampedReference侧重你改变了几次值,AtomicMarkableReference侧重你是否改变了值。
下面是一个示例:
public class AtomicStampReferenceTest {
/**
* @param args
*/
public static void main(String[] args) {
final AtomicStampedReference stampedReference = new AtomicStampedReference(10,0);
final int stamp = stampedReference.getStamp();
final int reference = stampedReference.getReference();
final AtomicMarkableReference markableReference = new AtomicMarkableReference(10,true);
final int getmarkAbleReference = markableReference.getReference();
final boolean mark = markableReference.isMarked();
System.out.println("stamp的值为:"+stamp+"\n"+"reference的值为:"+reference);
System.out.println("mark的值为:"+stamp+"\n"+"getmarkAbleReference的值为:"+reference);
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
boolean isUpdateSuccess = stampedReference.compareAndSet(reference, reference+10, stamp, stamp+1);
System.out.println(Thread.currentThread().getName()+" isUpdateSuccess:"+isUpdateSuccess);
boolean isMarkable = markableReference.compareAndSet(getmarkAbleReference, getmarkAbleReference+10, mark, false);
System.out.println(Thread.currentThread().getName()+" isMarkable:"+isMarkable);
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
boolean isUpdateSuccess = stampedReference.compareAndSet(reference, reference+10, stamp, stamp+1);
System.out.println(Thread.currentThread().getName()+" isUpdateSuccess:"+isUpdateSuccess);
boolean isMarkable = markableReference.compareAndSet(getmarkAbleReference, getmarkAbleReference+10, mark, false);
System.out.println(Thread.currentThread().getName()+" isMarkable:"+isMarkable);
}
});
try {
t1.start();
t1.join();
t2.start();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("stamp的值为:"+stampedReference.getStamp()+"\n"+"reference的值为:"+stampedReference.getReference());
System.out.println("mark的值为:"+ markableReference.isMarked()+"\n"+"getmarkAbleReference的值为:"+markableReference.getReference());
}
}
输出结果:
stamp的值为:0
reference的值为:10
mark的值为:0
getmarkAbleReference的值为:10
Thread-0 isUpdateSuccess:true
Thread-0 isMarkable:true
Thread-1 isUpdateSuccess:false
Thread-1 isMarkable:false
stamp的值为:1
reference的值为:20
mark的值为:false
getmarkAbleReference的值为:20
结果说明:
可以看到在线程t2更新stampedReference的值时失败了,因为版本号改变了或是标记(mark)改变了,所以AtomicStampedReference和AtomicMarkableReference可以帮我们解决CAS操作中的ABA问题。
注意
:AtomicMarkableReference它并不能解决ABA的问题 ,它是通过一个boolean来标记是否更改,本质就是只有true和false两种版本来回切换,只能降低ABA问题发生的几率,并不能阻止ABA问题的发生。
除了提供基本数据类型外,JDK还为我们准备了数组等复合结构。当前可用的原子数组有:AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray,分别表示整数数组、long型数组和普通对象数组。
下面列举AtomicIntegerArray的几个核心API
AtomicIntegerArray本质上是对int[]类型的封装。使用Unsafe类通过CAS的方式控制int[]在多线程下的安全性。它提供了以下几个核心API:
//获得数组第i个下标的元素
public final int get(int i)
//获得数组的长度
public final int length()
//将数组第i个下标设置为newValue,并返回旧的值
public final int getAndSet(int i, int newValue)
//进行CAS操作,如果第i个下标的元素等于expect,则设置为update,设置成功返回true
public final boolean compareAndSet(int i, int expect, intupdate)
//将第i个下标的元素加1
public final int getAndIncrement(int i)
//将第i个下标的元素减1
public final int getAndDecrement(int i)
//将第i个下标的元素增加delta(delta可以是负数)
public final int getAndAdd(int i, int delta)
下面是通过一个简单是示例来练习:
public class AtomicIntegerArrayTest {
static AtomicIntegerArray integerArray = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
int j = 0;
@Override
public void run() {
for(int i=0;i<10000;i++){
integerArray.getAndIncrement(i%integerArray.length());
}
}
}
public static void main(String[] args) {
Thread[] ts = new Thread[10];
for(int i=0;i<10;i++){
ts[i] = new Thread(new AddThread());
}
for(int i=0;i<10;i++){
ts[i].start();
}
for(int i=0;i<10;i++){
try {
ts[i].join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(integerArray);
}
}
输出结果为:
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
说明:
首先,新建了10个AtomicIntegerArray类型的数组,然后在Runable内对数组内10个元素进行累加操作,每个元素各加1000次,然后再开启10个线程。因此,可以预测,如果线程安全,数组内10个元素的值必然都是10000;反之,如果线程不安全,则部分或者全部数值会小于10000。
AtomicLongArray和AtomicReferenceArray的用法和AtomicIntegerArray 的用法都是大同小异,具体的方法在这里就不一一介绍了,用法同AtomicIntegerArray 。
基本原子类和数组原子类在最初设计编码时候就已经考虑到了需要保证原子性。但是往往有很多情况就是,由于需求的更改,原子性需要在后面加入,类似于我不要求你这整个类操作具有原子性,只要求类里面一个字段操作具有原子性。所以,AtomicIntegerFiledUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater就是这个作用。
AtomicIntegerFiledUpdater就是用来更新某一个实例对象里面的int属性的,但是需要注意的是,用法上有如下规则:
(1)字段必须是volatile类型的,在线程之间共享变量时保证立即可见。
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能过直接操作对象字段,那么就可以反射进行原子操作。
(3)对于父类的字段,子类时不能直接操作的,尽管子类可以访问父类的字段。
(4)只能是实例变量,不能是类变量,也就是说不能加static关键字
(5)只能是可修改变量,不能使final变量,因为final的语义就是不可修改的
(6)对于AtomicIntegerFiledUpdater和AtomicLongFiledUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFiledUpdater。
下面是AtomicLongFieldUpdater的主要函数列表:
// 受保护的无操作构造方法,供子类使用。
protected AtomicLongFieldUpdater()
// 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
long addAndGet(T obj, long delta)
// 如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract boolean compareAndSet(T obj, long expect, long update)
// 以原子方式将此更新器管理的给定对象字段当前值减 1。
long decrementAndGet(T obj)
// 获取此更新器管理的在给定对象的字段中保持的当前值。
abstract long get(T obj)
// 以原子方式将给定值添加到此更新器管理的给定对象的字段的当前值。
long getAndAdd(T obj, long delta)
// 以原子方式将此更新器管理的给定对象字段当前值减 1。
long getAndDecrement(T obj)
// 以原子方式将此更新器管理的给定对象字段的当前值加 1。
long getAndIncrement(T obj)
// 将此更新器管理的给定对象的字段以原子方式设置为给定值,并返回旧值。
long getAndSet(T obj, long newValue)
// 以原子方式将此更新器管理的给定对象字段当前值加 1。
long incrementAndGet(T obj)
// 最后将此更新器管理的给定对象的字段设置为给定更新值。
abstract void lazySet(T obj, long newValue)
// 为对象创建并返回一个具有给定字段的更新器。
static AtomicLongFieldUpdater newUpdater(Class tclass, String fieldName)
// 将此更新器管理的给定对象的字段设置为给定更新值。
abstract void set(T obj, long newValue)
// 如果当前值 == 预期值,则以原子方式将此更新器所管理的给定对象的字段设置为给定的更新值。
abstract boolean weakCompareAndSet(T obj, long expect, long update)
下面是一个简单是示例:
public class AtomicLongFiledUpdater {
/**
* @param args
*/
public static void main(String[] args) {
//获取student的class对象
Class cls = Student.class;
// 新建AtomicLongFieldUpdater对象,传递参数是“class对象”和“long类型在类中对应的名称”
AtomicLongFieldUpdater atoLong = AtomicLongFieldUpdater.newUpdater(cls, "id");
Student stu = new Student(123456L);
atoLong.compareAndSet(stu, 123456L, 1000);
System.out.println("id= "+stu.id);
}
}
输出的结果:
id= 1000
AtomicIntegerFieldUpdater是一个抽象类,但是它内部有一个private final类型的默认子类,所以在调用newUpdater的时候,会用模式子类来实现:
public static AtomicLongFieldUpdater newUpdater(Class tclass, String fieldName) {
if (AtomicLong.VM_SUPPORTS_LONG_CAS)
return new CASUpdater(tclass, fieldName);
else
return new LockedUpdater(tclass, fieldName);
}
newUpdater()的作用是获取一个AtomicIntegerFieldUpdater类型的对象。它实际返回的是CASUpdater对象,或者LockedUpdater对象;具体返回哪一个类取决于JVM是否支持long类型的CAS函数。CASUpdater和LockedUpdater都是AtomicIntegerFieldUpdater的子类,它们的实现类似。下面以CASUpdater来进行说明。
CASUpdater类的源码如下:
public boolean compareAndSet(T obj, long expect, long update) {
if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
return unsafe.compareAndSwapLong(obj, offset, expect, update);
}
它实际上是通过CAS函数操作。如果类的long对象的值是expect,则设置它的值为update。
AtomicIntegerFiledUpdater 和AtomicReferenceFieldUpdater实现思想基本一致,这里就不具体介绍了。
这篇文章主要是接收了多线程的原子类,包括了基本原子类、数组原子类、引用原子类、对象属性修改原子类。这四类中的各自思想基本一致,我就选取了一个或两个来介绍。下一篇,我会介绍学习锁。