分类: Java
2016-02-19 14:25:59
有时候,由于初期考虑不周,或者后期的需求变化,一些普通变量可能也会有线程安全的需求。如果改动不大,我们可以简单地修改程序中每一个使用或者读取这个变量的地方。但显然,这样并不符合软件设计中的一条重要原则——开闭原则。也就是系统对功能的增加应该是开发的,而对修改应该是相对保守的。而且,如果系统里使用到这个变量的地方特别多,一个一个修改也是一件令人厌烦的事情(况且很多使用场景下可能只是只读的,并无线程安全的强烈要求,完全可以保持原样)。
如果你有这种困扰,在这里根本不需要担心,因为在原子包里还有一个实用的工具类AtomicIntegerFieldUpdater。它可以让你在不改动(或者极少改动)原有代码的基础上,让普通的变量也享受CAS操作带来的线程安全性,这样你可以修改极少的代码,来获得线程安全的保证。这听起来是不是让人很激动呢?
根据数据类型不同,这个Updater有3种,分别是AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater。顾名思义,它们分别可以对int、long和普通对象就行CAS修改。
现在来思考这么一个场景。假设某地要进行一次选举。现在模拟这个机票场景,如果选民投了候选人一票,就记为1,否则记为0。最终的选票显然就是所有数据的简单求和。
public staticclass Candidate{
int id;
volatile intscore;
}
public finalstatic AtomicIntegerFieldUpdater
=AtomicIntegerFieldUpdater.newUpdater(Candidate.
//检查Updater是否工作正确
public staticAtomicInteger allScore=new AtomicInteger(0);
public staticvoid main(String[] args) throws InterruptedException {
finalCandidate stu=new Candidate();
Thread[]t=new Thread[10000];
for(int i = 0; i < 10000 ; i++) {
t[i]=new Thread() {
public void run() {
if(Math.random()>0.4){
scoreUpdater.incrementAndGet(stu);
allScore.incrementAndGet();
}
}
};
t[i].start();
}
for(int i = 0; i < 10000 ; i++) { t[i].join();}
System.out.println("score="+stu.score);
System.out.println("allScore="+allScore);
}
}
大家如果运行这段程序,不难发现,最终的Candidate.score总是和allScore绝对相等。这说明AtomicIntegerFieldUpdater很好地保证了Candidate.score的线程安全。
虽然AtomicIntegerFieldUpdater很好用,但是还是有几个注意事项:
第一,Updater只能修改它可见范围内的变量。因为Updater使用反射得到这个变量。如果变量不可见,就会出错。比如如果score申明为private,就是不可行的。
第二,为了确保变量被正确的读取,它必须是volatile类型的。如果我们原有代码中未申明这个类型,那么简单得申明一下就行,这不会引起什么问题。
第三,由于CAS操作会通过对象实例中的偏移量直接进行赋值,因此,它不支持static字段(Unsafe. objectFieldOffset()不支持静态变量)。
好了,通过AtomicIntegerFieldUpdater,是不是让我们可以更加随心所欲得对系统关键数据进行线程安全地保护呢?
摘自:实战Java高并发程序设计