2009-12-03 4 views
4

Позволяет сказать, что я взаимодействую с системой, которая имеет два увеличивающих счетчика, которые зависят друг от друга (эти счетчики никогда не будут уменьшаться): int totalFoos; // barredFoos plus nonBarredFoos int barredFoos;Основная проблема с потоками Java

У меня также есть два способа: int getTotalFoos(); // В основном сетевой вызов localhost int getBarredFoos(); // В основном сетевой вызов localhost

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

Каков наилучший способ получить точный счет как запрещенных, так и nonBarredFoos в один момент времени?

Полностью наивная реализация:

int totalFoos = getTotalFoos(); 
int barredFoos = getBarredFoos(); 
int nonBarredFoos = totalFoos - barredFoos; 

Это вопрос о том, что система может инкремент обоих счетчиков между двумя вызовами метода, а затем мои две копии будет синхронизирован и barredFoos будет иметь значение больше, чем когда было получено totalFoos.

Basic перепроверены реализация:

while (true) { 
    int totalFoos = getTotalFoos(); 
    int barredFoos = getBarredFoos(); 

    if (totalFoos == getTotalFoos()) { 
     // totalFoos did not change during fetch of barredFoos, so barredFoos should be accurate. 
     int nonBarredFoos = totalFoos - barredFoos; 
     break; 
    } 

    // totalFoos changed during fetch of barredFoos, try again 
} 

Это должно работать в теории, но я не уверен, что JVM гарантирует, что это то, что на самом деле происходит на практике один раз оптимизации и такой учитывается. Пример этих проблем см. В разделе http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html (ссылка через Ромена Мюллера).

Учитывая методы, которые у меня есть, и предположение выше, что счетчики фактически обновляются вместе, существует ли способ гарантировать, что мои копии двух счетчиков синхронизированы?

+1

Вы не должны полагаться на перепроверить, чтобы правильно работать в Java: HTTP: // www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html – Romain

+0

@RomainMuller благодарит за ссылку. Это именно то, о чем я говорил в своей озабоченности по поводу последнего примера выше. –

+0

Я полностью отметил эту статью в первый раз, когда я ее видел ... В Java есть много очень распространенных ошибок, которые почти все совершают, не зная, что зло скрывается в темноте ... – Romain

ответ

3

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

Конечно, одна вещь, с которой вы можете столкнуться с этим кодом, - бесконечный цикл; вы бы хотели быть уверены, что два значения, которые были изменены за такое короткое время, были бы весьма исключительной ситуацией, и даже тогда я думаю, что было бы разумно построить безопасную (то есть максимальное количество итераций), чтобы избежать получения в бесконечный цикл. Если значение, выходящее из этого счетчика, находится в коде, к которому у вас нет доступа, вы не хотите полностью полагаться на то, что на другом конце ничего не пойдет наперекосяк.

+1

+1. Чтобы не играть в retry-hokey-pokey, вам нужно будет синхронизировать свой метод с методами из системы, с которой вы взаимодействуете, что, конечно, невозможно в вашей ситуации. – Chris

+1

Его реализация недостаточна, так как виртуальной машине разрешено изменять порядок вызовов таким образом, чтобы вызов getBarredFoos() мог появиться после * обоих * вызовов getTotalFoos(). См. Подробное описание «Явного согласия на Java» Брайана Гёца. –

+0

Спасибо за ввод. В реальной реализации я учитываю возможный бесконечный цикл. –

1

Чтобы гарантировать согласованность чтения в потоках и предотвратить повторное упорядочение выполнения кода, особенно на машинах с muli-core, вам необходимо синхронизировать все права доступа на чтение и запись к этим переменным. Кроме того, чтобы убедиться, что в одном потоке вы видите самые последние значения , все переменные используются в текущем вычислении, которое необходимо синхронизировать при доступе к чтению.

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

См. Статью Брайана Гетца по телефону Java memory model.

+2

Какие переменные? Поля объявляются в системе, к которой у него нет доступа, и сделать локальные переменные volatile бессмысленными, поскольку к ним обращается только один поток. – meriton

+0

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

+0

@meriton. Я изменил свой ответ, пока вы отправили свой комментарий. – Joel

0

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

+0

Это так? Можете ли вы привести пример, когда его код потерпит неудачу? – meriton

+0

@meriton - если barredFoos изменился после проверки того, что totalFoos не изменился – Joel

+0

@Joel: зачем это важно? Код не извлекает текущие barredFoos после проверки того, что totalFoos не изменился, но использует значения, полученные ранее, что должно быть последовательным, поскольку totalFoos не изменилось. – meriton

0

Я собирался упомянуть AtomicInteger, как хорошо, но это не будет работать, потому что

1) У вас есть два целых числа, а не только один. AtomicIntegers вам не помогут. 2) У него нет доступа к базовому коду.

Мой вопрос, даже если вы не можете изменить базовый код, можете ли вы управлять , когда он выполнен? Вы можете поместить блоки синхронизации вокруг любых функций, которые изменяют эти счетчики. Это может быть не очень приятно (это может быть медленнее, чем ваша петля), но это, вероятно, будет «правильным» способом сделать это.

Если вы не можете управлять внутренними потоками, я думаю, ваш цикл будет работать.

И, наконец, если вы когда-нибудь получите контроль над кодом, лучше всего иметь одну синхронизированную функцию, которая блокирует доступ к обеим целым при ее запуске и возвращает их в int [].

+0

К сожалению, это ситуация, когда система, поддерживающая счетчики, не контролируется моей компанией. –

0

Учитывая, что нет никакого способа получить доступ к какому-либо механизму блокировки, поддерживающему инвариант «totalFoos = barredFoos + nonBarredFoos», невозможно гарантировать, что полученные вами значения согласуются с этим инвариантом. Сожалею.

+0

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

+0

Интересная точка. Я посмотрю. –

-1

Метод, который содержит код

while (true) {  
int totalFoos = getTotalFoos();  
int barredFoos = getBarredFoos();  
if (totalFoos == getTotalFoos()) {   
    int nonBarredFoos = totalFoos - barredFoos;   
    break;  
} 

}

должно быть синхронизировано

private synchronized void getFoos() 
+0

Почему? Что делает синхронизирующий однопоточный код? – meriton

+0

@Meriton. Thnaks за то, что указал на ошибку. Находясь в схеме ACORD за последнюю неделю, мои глаза были туманными. – ChadNC

+0

Пожалуйста, объясните причину падения, чтобы я мог понять мои ошибки. Доводка без объяснения нелепо и должна считаться спамом. – ChadNC

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