Java并发 AtomicInteger

1,181 阅读2分钟

前言:

AtomicInteger 是一个专门用于以线程安全方式更新整数设计的类。 为什么我们不能简单地使用volatile int ?

AtomicInteger

//非线程安全计数器 volatile int
public class CounterNotThreadSafe {
    private volatile int count = 0;
    public void increment() {
        count++;
    }
    public int getCount() {
        return count;
    }   
}

计数存储在volatile int #2行。使用volatile关键字来确保线程始终看到当前值(说明)。我们使用#4行的操作来递增计数器。检查是否是线程安全的使用以下测试:

public class ConcurrencyTestCounter {
    private final CounterNotThreadSafe counter = new CounterNotThreadSafe();
    @Interleave
    private void increment() {
        counter.increment();
    }
    @Test
    public void testCounter() throws InterruptedException {
        Thread first = new Thread( () ->    {  increment();  } ) ;
        Thread second = new Thread( () ->   {  increment();  } ) ;
        first.start();
        second.start();
        first.join();
        second.join();  
        assertEquals( 2 , counter.getCount());
    }
}

启动两个线程然后两个线程都使用线程join两个线程停止后,检查计数是否为2。

使用Interleave来自vmlens ;Interleave 注解告诉 vmlens 测试所有线程的交错的批注的方法。运行测试出现以下错误:

#逾期应该是:2 ; 但实际是:1
ConcurrencyTestCounter.testCounter:22 expected:<2> but was:<1>

原因是 ++ 不是原子操作,两个线程可以覆盖另一个线程的结果。两个线程首先并行读取变量计数。然后都写入变量。就会导致错误的值=1。vmlens报告:

使用AtomicInteger

public class CounterUsingIncrement {
    private final AtomicInteger count = new AtomicInteger();
    public  void increment() {
        count.incrementAndGet();
    }
    public int getCount() {
        return count.get();
    }   
}

或者:

public  void increment() {
    int current = count.get();
    int newValue = current + 1;
    while( ! count.compareAndSet( current , newValue ) ) {
        current = count.get();
        newValue = current + 1;
    }
}

现在由于该方法 incrementAndGet 是原子的,另一个线程总是在方法调用之前或之后看到该值,因此线程不能覆盖其计算的值。因此对于所有线程交错,计数现在总是=2。

结论

AtomicInteger 让我们以线程安全的方式更新整数。使用像incrementAndGetdecrementAndGet 的原子方法计算。并使用方法get和compareAndSet 进行计算。