Устранить состояние гонки в ExecutorService с newSingleThreadExecutor - PullRequest
0 голосов
/ 04 декабря 2018

Я написал следующую программу для понимания гонок:

import java.util.concurrent.*;

class RaceCount
{
    static int count = 0;

    public static void main(String [] args)
    {        
        ExecutorService executor = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 1000; i++)
        {
            executor.submit(new Callable<String>() {
                public String call() throws Exception {
                    count++; return "Incremented";
                }
            });
        }
        executor.shutdown();
        System.out.println(count);
    }
}

Очевидно, что число было намного меньше 1000. Итак, я изменил сигнатуру метода call () на:

public synchronized String call() throws Exception {

Но результат все равно был меньше 1000. Если я использую newFixedThreadExecutor (1000) вместо newSingleThreadExecutor , тогда я получу ожидаемую 1000, даже если вызов ()Метод не имеет префикса синхронизированное ключевое слово .
Итак, мои запросы:
1. Как синхронизировать потоки в случае newSingleThreadExecutor ?
2. Почему синхронизация не требуется, когда используется newFixedThreadExecutor ?

Ответы [ 2 ]

0 голосов
/ 04 декабря 2018

часть об отключении - это только половина решения.«public synchronized String call ()» синхронизирует вызов, так что только один поток может одновременно выполнить вызов одного экземпляра, но с помощью «executor.submit (new Callable ()» у вас есть 1000 экземпляров вашего вызова.в действительности нет синхронизации. Вы можете изменить это на "Callable call = new Callable () ..." вне цикла. И "executor.submit (call);" внутри так, чтобы у вас был один экземпляр вызова, который синхронизирован. Или изменитеот "int i" до "AtomicInteger i" и от ++ i до i.incrementAndGet ();

0 голосов
/ 04 декабря 2018

Ваша проблема не связана с состоянием гонки.Это происходит просто потому, что executor.shutdown() не ждет полного выключения перед возвратом.

Это из javadocs java.util.concurrent.ExecutorService.shutdown():

...Этот метод не ожидает завершения выполнения ранее представленных задач.Для этого используйте awaitTermination.

Другими словами, System.out.println(count) выполняется до запуска некоторых задач (хотя он обязательно запускается после отправки всех задач).

Я сделалнебольшое изменение в вашем коде, чтобы сделать этот факт очевидным:

public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();

    for (int i = 0; i < 1000; i++) {
        int e = i;
        executor.submit(new Callable<String>() {
            public String call() throws Exception {
                System.out.println("Executing " + e);
                count++;
                return "Incremented";
            }
        });
    }
    executor.shutdown();
    System.out.println("Count: " + count);
}

И вывод выглядит так:

...
Executing 835
Executing 836
Executing 837
Count: 837     <----- Printed before all tasks are run
Executing 838
Executing 839
Executing 840
Executing 841
...

Что ясно показывает, что задачи продолжают выполняться после того, как вы прочиталиcount переменная.

Если вам необходимо убедиться, что задачи выполняются до того, как вы прочитали обновленное значение, то вам, возможно, придется использовать awaitTermination, как показано ниже:

executor.shutdown();
executor.awaitTermination(3, TimeUnit.SECONDS); //Pick an appropriate timeout value
System.out.println("Count: " + count);
...