2014-11-23 1 views
1

У меня есть несколько потоков, пытающихся увеличить счетчик для определенного ключа в небезопасной пользовательской структуре данных (которую вы можете изобразить, чтобы быть похожим на HashMap). Мне было интересно, каким будет правильный способ увеличить счетчик в этом случае.Java - вызов функции синхронизированного геттера во время функции синхронизированного сеттера - правильный способ управления общей переменной?

Достаточно ли синхронизировать функцию приращения или мне также нужно синхронизировать операцию получения?

public class Example { 

private MyDataStructure<Key, Integer> datastructure = new CustomDataStructure<Key, Integer>(); 

private class MyThread implements Runnable() { 

    private synchronized void incrementCnt(Key key) { 
     // from the datastructure documentation: if a value already exists for the given key, the 
     // previous value will be replaced by this value 
     datastructure.put(key, getCnt(key)+1); 

     // or can I do it without using the getCnt() function? like this: 
     datastructure.put(key, datastructure.get(key)+1)); 
    } 

    private synchronized int getCnt(Key key) { 
     return datastructure.get(key); 
    } 

    // run method... 
} 
} 

Если у меня есть два потока, t1, t2, например, я бы что-то вроде:

t1.incrementCnt(); 
t2.incrmentCnt(); 

Может ли это привести к какой-либо тупик? Есть ли лучший способ решить эту проблему?

+0

Без взаимоблокировки, но если это единственный метод, который пишет cnt, вы можете просто сделать ++ cnt из incrementCnt() – Shmoopy

+2

Этот код фактически не компилируется. Вы не можете применить оператор '++' к вызову метода. Он должен быть переменной. Это не просто nitpick: такого рода вещи должны сказать вам, что вы делаете это неправильно, по-язычному, даже если механизм потока прекрасен, а это не так. – RealSkeptic

+0

Эти причины включают в себя не позволить использовать вашу собственную небольшую версию atominteger? – Fildor

ответ

3

Основная проблема с этим кодом состоит в том, что он может не обеспечить синхронизацию доступа к datastructure, так как доступ кодов синхронизируется на this внутреннего класса. Что различно для разных экземпляров MyThread, поэтому взаимного исключения не произойдет.

Более правильный путь, чтобы сделать datastructure в final поле, а затем синхронизировать на нем:

private final MyDataStructure<Key, Integer> datastructure = new CustomDataStructure<Key, Integer>(); 

private class MyThread implements Runnable() { 

    private void incrementCnt(Key key) { 
     synchronized (datastructure) { 
      // or can I do it without using the getCnt() function? like this: 
      datastructure.put(key, datastructure.get(key)+1)); 
     } 
    } 

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

+0

. Все доступ к данным осуществляется через функцию incrementCnt(), поэтому я поеду с вашим предложением. Благодарим вас за помощь. – SeanKw

+0

заявляет, что структура данных является «окончательной» действительно необходимой здесь или просто хорошей практикой? объект datastructure не будет отскакивать, чтобы ссылаться на другой объект. – SeanKw

+1

@SeanKw Если вы на 200% уверены, что ссылка не будет перезаписана, вы можете опустить «final», но только для того, чтобы быть на более безопасной стороне, я всегда буду использовать «final» в таких случаях. –

2

Как вам сказал другой ответ, вы должны синхронизировать свою структуру данных, а не объект thread/runnable. Общей ошибкой является попытка использовать синхронизированные методы в потоке или исполняемом объекте. Блокировки синхронизации основаны на экземплярах, а не на классах (если только этот метод не является статическим), а при запуске нескольких потоков это означает, что на самом деле существует несколько экземпляров потоков.

Менее ясны о Runnables: вы можете использовать один экземпляр класса Runnable с несколькими потоками. Так что в принципе вы могли бы синхронизировать на нем. Но я все еще думаю, что это плохая форма, потому что в будущем вам может понадобиться создать несколько экземпляров и получить очень неприятную ошибку.

Таким образом, наилучшей практикой является синхронизация по фактическому элементу, к которому вы обращаетесь.

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

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

Благодарим вас за подробное объяснение блокировок синхронизации. Структура данных, к сожалению, не под моим контролем, поэтому я искал потокобезопасный способ доступа к ней. Мне нужна была структура данных, такая как ConcurrentHashMap, но по определенным причинам я должен придерживаться структуры пользовательских данных. – SeanKw

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