Для конкретной потокобезопасной структуры данных мне необходимо защитить доступ к центральной структуре данных (а именно к массиву байтов). Я предпочитаю использовать ReentrantLocks в этом случае, поскольку это политика справедливости, а также расширенные возможности при создании нескольких условий.Несколько условий против множественных замков
Условия параллельности являются сложными и перечислены ниже:
- Центральный массив должен быть защищен исключительно (то есть один поток сразу).
- Два метода доступа (foo и bar) должны иметь возможность запускаться одновременно (блокируя внутренне, если они попытаются получить доступ к массиву центрального байта).
- Призывы к любому методу (foo и bar) должны быть эксклюзивными (то есть множественные вызовы foo из разных потоков приведут к блокировке потока).
В моей первоначальной реализации, я решил реализовать два вложенных замки, как показано ниже:
ReentrantLock lockFoo = new ReentrantLock(true);
ReentrantLock lockCentral = new ReentrantLock(true);
Condition centralCondition = lockCentral.newCondition();
public void foo(){
// thread-safe processing code here
lockFoo.lock();
lockCentral.lock();
try{
// accessing code here
try{
// waits upon some condition for access
while(someCondition){
centralCondition.await();
}
}catch(InterruptedException ex){
// handling code here
}
// more processing
}finally{
lockCentral.unlock();
lockFoo.unlock();
}
}
структура эквивалентна по методу bar
, просто с другим объектом блокировки lockBar
. Кроме того, код упростил мое более сложное многоуровневое ожидание и сигналы к одному условию для простоты.
Используя это, я не могу не чувствовать, что код кажется излишне сложным и неясным в том, что не только что есть два замка вложенных, но и одна попытка - наконец, не говоря уже о том, как lockCentral
может быть выпущен и повторно приобретен несколькими раз в то время как lockFoo
проводится повсюду.
Вместо этого я пытался реорганизовать внешний замок (lockFoo
и lockBar
) в качестве условия lockCentral
вместо этого, как показано ниже:
ReentrantLock lockCentral = new ReentrantLock(true);
Condition fooCondition = lockCentral.newCondition();
Condition centralCondition = lockCentral.newCondition();
boolean isInFoo = false;
public void foo(){
// thread-safe processing code here
lockCentral.lock();
try{
// implement method exclusiveness via fooCondition
try{
while(isInFoo){
fooCondition.await();
}
isInFoo = true;
}catch(InterruptedException ex){
return;
}
// accessing code here
try{
// waits upon some condition for access
while(someCondition){
centralCondition.await();
}
}catch(InterruptedException ex){
// handling code here
}
// more processing
}finally{
isInFoo = false;
fooCondition.signal();
lockCentral.unlock();
}
}
после некоторого осмотра я не могу решить между ли бывший является лучшая идея или последняя (особенно с включением этого случайного булева). Идея упрощения кода, по-видимому, привела к тому, что код длиннее, в этом случае очень контрастно интуитивно понятный.
Есть некоторые конвенции или веские причины, чтобы дать аргумент либо:
Используя один замок за блокировки контекста (прежний код, в котором различные причины блокировки акций различных замков).
Использование одного замка за каждый ресурс блокировки (последний код, где центральная структура, которая должна быть защищена, использует единый замок, а все остальные реализованы как условия для доступа к указанной структуре).
Хм ... Я вижу вашу точку. Я не думал о выигрыше в производительности от последнего, пытается ли блокировать ReentrantLock, что приводит к существенной деградации производительности (под угрозой)? Особенно с накладными расходами на доступ к глобальному 'lockCentral' (поскольку' fooCondition' должен каким-то образом распознать центральный мьютекс). Тесты не дают мне окончательных результатов на моем компьютере, и это действительно очень важный сегмент кода. – initramfs