2016-04-30 4 views
3

Я пытаюсь запустить несколько потоков. Я ясно получаю состояние гонки и в состоянии решить ее следующим образом:Состояние гонки даже после синхронизации

final Data data = new Data(); 
for (int i = 0; i < numberOfThreads; i++) { 
    final Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      //using this sync block to stop the race condition 
      synchronized (data){ 
       final int value = data.getValue(); 
       data.setValue(value + 1); 
      } 
     } 
    }); 
    thread.start(); 
} 

Но я не хочу, чтобы синхронизировать на этом блоке, и вместо этого хотите, чтобы справиться с этим в классе данных. Поэтому я удалил вышеупомянутый блок синхронизации и вместо этого синхронизировал методы get и set в классе Data следующим образом, но это все еще вызывает условия гонки. Почему проблема, хотя я их синхронизировал?

public class Data { 

    private int value; 

    public synchronized int getValue(){ 
     return this.value; 
    } 

    public synchronized void setValue(int num){ 
     this.value = num; 
    } 
} 
+0

Поскольку без дополнительного синхронизирующего блока несколько потоков могут одновременно вызвать getValue(), включить его одним, а затем записать одно и то же значение обратно, даже если они должны увеличиваться на количество раз, когда вызывался getValue(). – markspace

+0

Как точно проявляется состояние гонки? Кроме того, если у вас есть необходимость увеличения потокобезопасности, почему бы вам не использовать [AtomicInteger] (https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/AtomicInteger. html)? –

+0

Это вариант блокировки двойной проверки (https://en.wikipedia.org/wiki/Double-checked_locking) - как показывают ответы, вы получаете, а затем устанавливаете отдельно. – stdunbar

ответ

2

Добавление synchronized к индивидуальным методам аналогичен делать что-то вроде этого

final Data data = new Data(); 
for (int i = 0; i < numberOfThreads; i++) { 
    final Thread thread = new Thread(new Runnable() { 
     @Override 
     public void run() { 
      synchronized (data){ 
       final int value = data.getValue(); 
      } 
      synchronized (data){ 
       data.setValue(value + 1); 
      } 
     } 
    }); 
    thread.start(); 
} 

Где нить может очень четко застрять между ГЭТ и множеством. Чтобы решить эту проблему, вам нужно либо добавить новый синхронизированный метод в класс Data, который выполняет задачу value + 1, либо обертывает обе строки в блоке , как это было в вашем коде.

4

Потому что вы этого не сделали. Вы синхронизировали любой из них, поэтому два потока не могут одновременно выполнять эти методы, но один поток может выполнить getValue(), а затем заканчивает getValue() и до того, как он войдет в setValue(), другой поток получает свою очередь и вызывает getValue(), что является совершенно законным и вашим состоянием гонки.

КПП. на всякий случай Data будет вашим целым классом, AtomicInteger будет таким же, но сделано правильно. Там вы д. г. имеют метод incrementAndGet(), который выполняет операцию чтения и записи в один блок блокировки, который является важным моментом в вашем случае.

1

Прежде всего, в вашем Data класс value должен быть volatile.

Что касается вопроса, который вы упомянули; Вы не можете изменить код, как вы. Так, как вы изменили его к следующему может произойти:

1) value в Data является 0

2) Thread 0 читает value (читает 0)

3) Thread 1 читает value ((0)

4) Thread 1 Приращенияи записывает новое значение Data.value (пишет 1)

5) Thread 0 приращения value и записывает новое значение Data.value (пишет 1)

Проблема здесь в том, что на стадии 5) 1 был написанное с Thread 0 не знает, что Thread 1 уже сгенерировано value с Thread 0value.

+0

Использование volatile не имеет значения, если несколько потоков взаимодействуют только с этим полем внутри синхронизированных блоков. – KookieMonster

+0

@KookieMonster 'volatile' имеет 2 функции в Java; 1) Чтобы убедиться, что частичное значение не считывается/не записывается 2) Чтобы заставить значение не храниться в локальном кеше/памяти потока В этом случае вам, очевидно, не нужно беспокоиться о случае 1 (технически, вам никогда не придется беспокоиться об этом с помощью 'int') Однако, если вы не используете 'volatile', разные потоки могут не увидеть самое последнее значение, если' int' обновляется другим потоком, из-за чего в Java каждый поток имеет свой собственный кеш – Tmr

+0

В общем, это правильно. Но 'volatile' не требуется« обновлять »значения при использовании' synchronized', поскольку значения уже «обновлены» в конце «синхронизированного» блока (это не означает, что 'volatile' и' synchronized' сделать другую избыточную, вам просто не нужно «volatile» в этой конкретной ситуации). – KookieMonster

0

Ваше добавление синхронизировано на уровне метода. Таким образом, один поток может вызвать getValue(), и можно вызвать setValue(). Вы можете полностью удалить синхронизацию и изменить тип частного элемента value на AutomicInteger. Затем используйте поточно-безопасный метод в этом классе.

+0

Синхронизировать по методу невозможно. Единственное, что может когда-либо синхронизировать ваш код, - это объект. Синхронизированный метод экземпляра синхронизирует объект 'this', и синхронизированный метод класса синхронизируется с объектом класса. –

+0

Спасибо. Я исправил свой пост. –

Смежные вопросы