Несколько потоков и AtomicInteger

Я взял этот код из учебника по атомике, он гласил: -

"Используя AtomicInteger в качестве замены Integer, мы можем увеличивать число одновременно в поточно-ориентированном поместье без синхронизации доступа к переменной. Метод incrementAndGet () является атомарной операцией, поэтому мы можем безопасно вызывать этот метод из нескольких потоки."

Он сказал, что это вернет правильный результат, 1000, однако ни один экземпляр не достигает 1000, они обычно значительно меньше, например.

test1 result = 532
test2 result = 128

Что случилось ?

public class AtomicsTest {

    public static void main(String... args){

        AtomicsTest test = new AtomicsTest();        
        test.test1();        
        test.test2();                             
    }

    public void test1() {
        AtomicInteger atomicInt = new AtomicInteger(0);

        ExecutorService executor = Executors.newSingleThreadExecutor();                
        IntStream.range(0,1000).forEach(i->executor.submit( atomicInt::incrementAndGet ));
        System.out.println("test1 result = "+ atomicInt.get());
        executor.shutdown();
    }

    public void test2() {

        AtomicInteger atomicInt = new AtomicInteger(0);

        ExecutorService executor = Executors.newFixedThreadPool(2);            
        IntStream.range(0,1000).forEach(i->executor.submit( atomicInt::incrementAndGet ));
        System.out.println("test2 result = " + atomicInt.get());        
        executor.shutdown();           
    }    
}

person Chris Milburn    schedule 29.03.2017    source источник
comment
Помимо предоставленного ответа, ваш пример на самом деле не проверяет синхронизацию атомарной переменной. Вы используете однопоточный исполнитель для увеличения вашей переменной, поэтому даже с неатомарной переменной вы увеличиваете ее по одному и в результате получаете 1000, поскольку только 1 поток обращается к переменной в любое время.   -  person pandaadb    schedule 29.03.2017
comment
@pandaadb, это может быть правдой для его первого теста, но не для второго. Я не сразу заметил, но разница только в исполнителе.   -  person Michael    schedule 30.03.2017
comment
@Michael, ты абсолютно прав. Я не заметил вторую, но увидел первую :) Я подумал, что стоит указать   -  person pandaadb    schedule 30.03.2017


Ответы (1)


Ваша проблема в том, что вы сразу же распечатываете значение после отправки потоков, несмотря на то, что не все потоки обязательно завершились.

public void test1() {
    AtomicInteger atomicInt = new AtomicInteger(0);

    ExecutorService executor = Executors.newSingleThreadExecutor();                
    IntStream.range(0,1000).forEach(i->executor.submit( atomicInt::incrementAndGet ));

    // All threads submitted (not necessarily finished)

    System.out.println("test1 result = "+ atomicInt.get());

    executor.shutdown();

    // Threads are still not necessarily done
}

Если вы явно дожидаетесь завершения работы службы исполнителя, прежде чем печатать значение, вы должны увидеть ожидаемый результат:

public void test1() {
    try {
        AtomicInteger atomicInt = new AtomicInteger(0);

        ExecutorService executor = Executors.newSingleThreadExecutor();
        IntStream.range(0,1000).forEach(i->executor.submit( atomicInt::incrementAndGet ));

        executor.shutdown();
        // Wait a maximum of ~a million billion years
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

        System.out.println("test1 result = "+ atomicInt.get());
    }
    catch (InterruptedException ex)
    {
        // Oh no!
    }
}
person Michael    schedule 29.03.2017
comment
Я склонен ждать исполнителей Long.MAX_VALUE как лучшего шаблона. - person Gray; 29.03.2017
comment
Хорошая точка зрения. Я отредактировал его. Мне нравится идея, что он может ждать буквально миллион миллиардов лет. - person Michael; 29.03.2017