java.uitl.concurrent.atomic
包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其它操作的原子性。例如,AtomicInterger
类提供了方法incrementAndGet
和decrementAndGet
,它们分别以原子方式将一个整数自增或自减。
AtomicInteger atomicInteger = new AtomicInteger(1);
//以原子方式将一个整数自增1
int addAndGet = atomicInteger.incrementAndGet();
//以原子方式将一个整数自减1
int decrementAndGet = atomicInteger.decrementAndGet();
incrementAndGet
方法以原子方式将AtomicLong
自增,并返回自增后的值。也就是说,获得值,增1并设置然后生成新值的操作不会中断。可以保证即使是多个线程并发地访问同一个实例,也会计算并返回正确的值。如果希望完成更复杂的更新,就必须使用compareAndSet
方法。例如,假设希望跟踪不同线程观察的最大值。
public static AtomicLong largest = new AtomicLong(5);
do{
oldValue = largest.get();
long observed = 10l;
newValue = Math.max(oldValue, observed);
}while (!largest.compareAndSet(oldValue,newValue));
如果另一个线程也在更新largest
,就可能阻止这个线程更新。这样一来,compareAndSet
会返回false
,而不会设置新值。在这种情况下,循环会多次尝试,读取更新后的值,并尝试修改。最终,它会成功地用新值替换原来的值。 实际上,可以提供一个lambda表达式更新变量
largest.updateAndGet(x->Math.max(x,observed));
或
largest.accumulateAndGet(observed,Math::max);
还有getAndUpdate和getAndAccumulate方法返回原值
largest.getAndUpdate(x -> Math.max(x, observed));
或
largest.getAndAccumulate(observed,Math::max);
如果有大量线程要访问相同的原子值,性能会大幅下降,因为乐观更新需要太多次重试。JavaSE
8提供了LongAdder
和LongAccumulator
类来解决这个问题。LongAddr
包括多个变量(加数),其总和为当前值。可以有多个线程更新不同的加数,线程个数增加时会自动提供新的加数。通常情况下,只有当所有工作都完成之后才需要总和的值,对于这种情况,这种方法会很高效。性能会显著的提升。 代码结构如下
final LongAdder adder = new LongAdder();
for(...)
pool.submit(() -> {
while (...){
...
if(...)
//自增1,无返回值
adder.increment();
}
});
...
long total = adder.sum();
LongAccumulator
将这种思想推广到任意的累加操作。在构造器中,可以提供这个操作以及它的零元素。要加入新的值,可以调用accumulate
。调用get来获得当前值。下面代码可以得到与LongAddr
同样的效果。
LongAccumulator adder = new LongAccumulator(Long::sum, 0);
adder.accumulate(value);
在内部,这个累加器包含a1,a2,…,an。每个变量初始化为零元素。 调用accumulate
并提供值v时,其中一个变量会以原子方式更新为ai = ai op v ,这里op是中缀形式的累加操作。这个例子中,调用accumulate
会对某个i计算ai = ai + v。 get的结果是a1 op a2 op … op an。这就是累加器的总和:a1+a2+a3+…+an。 另外DoubleAdder
和DoubleAccumulator
也采用同样的方式。