Безопасен ли поток статического счетчика в многопоточном приложении? - PullRequest
2 голосов
/ 19 ноября 2011
public class counting
{
  private static int counter = 0;

  public void boolean counterCheck(){
  counter++;
  if(counter==10)
  counter=0;
  }
}

Метод counterCheck может быть доступен нескольким потокам в моем приложении.Я знаю, что статические переменные не являются потокобезопасными.Я был бы признателен, если бы кто-то мог помочь мне с примером или дать мне причину, почему я должен синхронизировать метод или блок.Что будет, если я не синхронизируюсь?

Ответы [ 6 ]

6 голосов
/ 19 ноября 2011

Это явно не потокобезопасно.Рассмотрим два потока, которые работают идеально параллельно.Если счетчик равен 9, каждый из них будет увеличивать счетчик, в результате чего счетчик будет равен 11. Ни один из них не увидит этот счетчик равным 10, так что счетчик будет продолжать увеличиваться с этого момента, а не переносится, как предполагалось.

2 голосов
/ 19 ноября 2011

Это не потокобезопасно, И этот шаблон обновления счетчика из нескольких потоков, вероятно, является # 1 способом достижения отрицательного масштабирования (он работает медленнее, когда вы добавляете больше потоков) многопоточногоapplication.

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

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

0 голосов
/ 19 ноября 2011

первый из counter++ сам по себе НЕ является потокобезопасным

аппаратные ограничения делают его эквивалентным

int tmp = counter;
tmp=tmp+1;
counter=tmp;

, и что происходит, когда одновременно существуют 2 потока?одно обновление потеряно, вот что

вы можете сделать этот поток безопасным с помощью atomicInteger и цикла CAS

private static AtomicInteger counter = new AtomicInteger(0);

public static boolean counterCheck(){
    do{
        int old = counter.get();
        int tmp = old+1;
        if(tmp==10)
            tmp=0;
        }
    }while(!counter.compareAndSet(old,tmp));
}
0 голосов
/ 19 ноября 2011

Это НЕ ориентировано на многопотоковое исполнение по нескольким причинам.Наиболее очевидным является то, что у вас может быть две темы, идущие от 9 до 11, как указано в других ответах.

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

Либо один поток может внести несколько изменений, а другой всегда будет видеть 0, поскольку из-за модели памяти Java другой поток может видеть значение, кэшированное в регистре.

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

0 голосов
/ 19 ноября 2011

Представьте себе counter равно 9.

Тема 1 делает это:

counter++; // counter = 10

Тема 2 делает это:

counter++; // counter = 11
if(counter==10) // oops

Теперь вы можете подумать, что можете это исправить с помощью:

if(counter >= 10) counter -= 10;

Но теперь, что произойдет, если оба потока проверит условие и обнаружит, что оно истинно, тогда оба потока уменьшат счетчик на 10 (теперь ваш счетчик отрицателен).

Или на еще более низком уровне, counter++ - это на самом деле три операции:

  • Получить counter
  • Добавить один к counter
  • Магазин counter

Итак:

  1. поток 1 получает счетчик
  2. поток 2 получает счетчик
  3. Оба потока добавляют один к своему счетчику
  4. Оба потока хранят свой счетчик

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

c1 = counter;
c2 = counter;
c1 = c1 + 1;
c2 = c2 + 1;
counter = c1; // Note that this has no effect since the next statement overrides it
counter = c2;

Таким образом, вы можете обернуть его в блок synchronized, но лучше использовать AtomicInteger , если у вас всего несколько потоков:

public class counting {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void counterCheck() {
        int value = counter.incrementAndGet();
        // Note: This could loop for a very long time if there's a lot of threads
        while(value >= 10 && !counter.compareAndSet(value, value - 10)) {
            value = counter.get();
        }
    }
}
0 голосов
/ 19 ноября 2011

Самая большая опасность?Два шага до counter перед проверкой counter == 10, поэтому сброс до 0 невозможен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...