Параллельность, видимость объекта - PullRequest
7 голосов
/ 14 декабря 2009

Я пытаюсь выяснить, не страдает ли приведенный ниже код от каких-либо потенциальных проблем параллелизма. В частности, проблема видимости связана с изменчивыми переменными. Volatile определяется как: Значение этой переменной никогда не будет кэшироваться локально в потоке: все операции чтения и записи будут идти прямо в «основную память»

public static void main(String [] args)
{
    Test test = new Test();

    // This will always single threaded
    ExecutorService ex = Executors.newSingleThreadExecutor();

    for (int i=0; i<10; ++i)
        ex.execute(test);
}

private static class Test implements Runnable {
    // non volatile variable in question
    private int state = 0;

    @Override
    public void run() {
        // will we always see updated state value? Will updating state value
        // guarantee future run's see the value?
        if (this.state != -1)
            this.state++;
    }
}

Для вышеуказанного однопоточный исполнитель :

Можно ли сделать test.state энергонезависимым? Другими словами, будет ли каждый последующий Test.run () (который будет происходить последовательно, а не одновременно, потому что исполнитель снова является однопоточным) всегда видеть обновленное значение test.state ? Если нет, то не гарантирует ли выход из Test.run () каких-либо изменений, внесенных потоком локально, обратно в основную память? Иначе, когда изменения, сделанные потоком, локально записываются обратно в основную память, если нет при выходе из потока?

Ответы [ 6 ]

4 голосов
/ 14 декабря 2009

Пока это только один поток, нет необходимости делать его изменчивым. Если вы собираетесь использовать несколько потоков, вы должны не только использовать volatile, но и синхронизировать. Увеличение числа не является атомарной операцией - это распространенное заблуждение.

public void run() {
    synchronize (this) {
        if (this.state != -1)
            this.state++;
    }
}

Вместо синхронизации вы также можете использовать AtomicInteger # getAndIncrement () (если вам не нужно, если раньше).

private AtomicInteger state = new AtomicInteger();

public void run() {
    state.getAndIncrement()
}
3 голосов
/ 14 декабря 2009

Первоначально я думал так:

Если задача всегда выполнялась той же ветки, не было бы проблема. Но Excecutor производится newSingleThreadExecutor() может создать новые темы, чтобы заменить те, которые убиты по любой причине. Здесь нет гарантия о том, когда замена поток будет создан или какой поток создам его.

Если поток выполняет некоторые записи, то звонки start() на новую тему, те записи будут видны новым нить. Но нет никаких гарантий, что это правило применяется в этом случае.

Но неоспоримый прав: создание правильного ExecutorService без достаточных барьеров для обеспечения видимости практически невозможно. Я забыл, что обнаружение смерти другого потока - это синхронизация с . Механизм блокировки, используемый для простоя рабочих потоков, также требует барьера.

2 голосов
/ 14 декабря 2009

Да, это безопасно, даже если исполнитель заменил свою нить посередине. Начало / конец потока также являются точками синхронизации.

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

Простой пример:

static int state;
static public void main(String... args) {
    state = 0;                   // (1)
    Thread t = new Thread() {
        public void run() {
            state = state + 1;   // (2) 
        }
    };
    t.start();
    t.join();
    System.out.println(state);  // (3)
}

Гарантируется, что (1), (2), (3) хорошо упорядочены и ведут себя как ожидалось.

Для однопотокового исполнителя «Задачи гарантированно выполняются последовательно», он должен каким-то образом определить завершение одной задачи перед началом следующей, что обязательно правильно синхронизирует различные run() '

0 голосов
/ 14 декабря 2009

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

Однако, если вы хотите использовать значение состояния в основном потоке, который выполняет цикл, вы должны сделать поле volatile:

    for (int i=0; i<10; ++i) {
            ex.execute(test);
            System.out.println(test.getState());
    }

Однако даже это может работать некорректно с volatile, потому что синхронизация между потоками отсутствует.

Поскольку поле является закрытым, возникает проблема, только если основной поток выполняет метод, который может получить доступ к этому полю.

0 голосов
/ 14 декабря 2009

Ваш код, особенно этот бит

            if (this.state != -1)
                    this.state++;
Для

потребуется атомарный тест значения состояния и затем приращение к состоянию в параллельном контексте. Таким образом, даже если ваша переменная изменчива и в ней участвует более одного потока, у вас могут возникнуть проблемы с параллелизмом.

Но ваш дизайн основан на утверждении, что всегда будет только один экземпляр Test, и , этот единственный экземпляр предоставляется только одному (одному) потоку. (Но обратите внимание, что один экземпляр фактически является общим состоянием между основным потоком и потоком исполнителя.)

Я думаю, вам нужно сделать эти предположения более явными (например, в коде используйте ThreadLocal и ThreadLocal.get ()). Это необходимо для защиты как от будущих ошибок (когда какой-либо другой разработчик может небрежно нарушать предположения проекта), так и от принятия предположений о внутренней реализации используемого вами метода Executor , который в некоторых реализациях может просто обеспечить однопоточный исполнитель (т.е. последовательный и не обязательно один и тот же поток при каждом вызове execute (runnable).

0 голосов
/ 14 декабря 2009

Если ваш ExecutorService является однопоточным, то нет общего состояния, поэтому я не вижу, как могут возникнуть какие-либо проблемы вокруг этого.

Однако не имеет ли больше смысла передавать новый экземпляр вашего Test класса на каждый вызов execute()? т.е.

for (int i=0; i<10; ++i)
    ex.execute(new Test());

Таким образом, не будет общего состояния.

...