2012-05-22 4 views
14

Мне нужен ReadWriteLock, который НЕ реентерабелен, потому что блокировка может быть выпущена другим потоком, чем тот, который его приобрел. (Я понял это, когда я начал периодически прерывать IllegalMonitorStateException.)Есть ли не реентерабельный ReadWriteLock, который я могу использовать?

Я не уверен, что нереферентом является правильный термин. ReentrantLock позволяет потоку, который в настоящее время удерживает, блокировать, чтобы снова получить его. Я НЕ хочу этого поведения, поэтому я называю его «неретеррантным».

Контекст заключается в том, что у меня есть сервер сокетов с использованием пула потоков. В каждом соединении нет нити. Запросы могут обрабатываться различными потоками. Клиентскому соединению может потребоваться блокировка в одном запросе и разблокировка в другом запросе. Поскольку запросы могут обрабатываться разными потоками, я должен иметь возможность блокировки и разблокировки в разных потоках.

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

Это ReadWriteLock, потому что мне нужно разрешить несколько «читателей» или эксклюзивный «писатель».

Похоже, это может быть написано с использованием AbstractQueuedSynchronizer, но я боюсь, что если я напишу сам, сделаю какую-то тонкую ошибку. Я могу найти различные примеры использования AbstractQueuedSynchronizer, но не ReadWriteLock.

Я мог бы взять источник OpenJDK ReentrantReadWriteLock и попытаться удалить реентерабельную часть, но, боюсь, я не получу ее совершенно правильно.

Я смотрел в Guava и Apache Commons, но не нашел ничего подходящего. У Apache Commons есть RWLockManager, который может делать то, что мне нужно, но я не уверен, и это кажется более сложным, чем мне нужно.

ответ

24

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

final int PERMITS = Integer.MAX_VALUE; 
Semaphore semaphore = new Semaphore(PERMITS); 

// read 
semaphore.acquire(1); 
try { ... } 
finally { 
    semaphore.release(1); 
} 

// write 
semaphore.acquire(PERMITS); 
try { ... } 
finally { 
    semaphore.release(PERMITS); 
} 
+0

Спасибо, похоже, это может сделать трюк. Чтение документов для Семафора звучит так, как будто мне нужно указать «справедливый» вариант. «Этот класс также предоставляет удобные методы для одновременного получения и выпуска нескольких разрешений. Остерегайтесь повышенного риска неопределенной отсрочки, когда эти методы используются без справедливости. –

+0

И я вижу, что Семафор реализован (по крайней мере, в OpenJDK) с AbstractQueuedSynchronizer –

+0

Удивительный! Я оказываюсь в одной лодке (нужен r/w-замок за многопоточным/объединенным бережливым сервером). Этот вопрос и ответ - вот что мне нужно! Благодаря! – akagixxer

5

Я не уверен, что понимаю вопрос. Неотправляемый ReadWriteLock представляет собой оксюморонную ИМО, дающую слово "reentrant" has specific meaning в народной резьбе. Он должен использоваться из нескольких потоков, иначе он не будет работать. Существует только ReentrantReadWriteLock, который реализует ReadWriteLock в Java 6 JDK, который я вижу. Конечно, множественные чтения могут блокировать ReadWriteLock в то же время, но использовать слово «реентерабель» в этом контексте является сбивающим с толку.

Что вы должны делать, это хороший код для блокировки и разблокировки. Например, всегда используйте блоки try {} finally, чтобы убедиться, что вы получаете каждую разблокировку.

ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 
Lock readLock = readWriteLock.readLock(); 
... 
readLock.lock(); 
try { 
    ... // do stuff with the data 
} finally { 
    readLock.unlock(); 
} 

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

выглядит как это может быть написано с использованием AbstractQueuedSynchronizer

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

Я начал получать IllegalMonitorStateException периодически

Я не был бы быстро винить ReadWriteLock для этого. Я подозреваю, что у вас есть код, который не блокировки коллекции, или что вы произвольно изменяете коллекцию в блокировке чтения.

+3

Отличный ответ. Если ваши блокировки разблокированы в другом потоке, вы не применяете их правильно. – derekerdmann

+3

Невозвратный может быть неправильным термином. ReentrantLock позволяет потоку, который в настоящее время удерживает, блокировать, чтобы снова получить его. Я НЕ хочу этого поведения, поэтому я назвал его «не реентеративным». Контекст заключается в том, что у меня есть сервер сокетов, использующий несколько пулов потоков. В каждом соединении нет нити. Запросы могут обрабатываться различными потоками. Клиентскому соединению может потребоваться блокировка в одном запросе и разблокировка в другом запросе. Поскольку запросы могут обрабатываться разными потоками, я должен иметь возможность блокировки и разблокировки в разных потоках. –

+1

Это кажется слишком сложным @ Andrew. Вы можете подумать о том, чтобы сделать один поток на соединение, если вы не планируете иметь 10,00 одновременных подключений. Если вы должны остаться с текущей конфигурацией, я бы заблокировал транзакцию с одним клиентом. У меня не было бы блокировки чтения, а затем следующий клиент trxn, обработанный следующим потоком, разблокирует его.Не зная больше о вашей программе, к сожалению, трудно помочь. – Gray

1

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

Вы можете удалить задачу с карты или заменить ее специальным «объектом блокировки», чтобы указать, что она заблокирована. Вы можете вернуть задачу с обновленным состоянием на карту, чтобы разрешить другой поток, или, альтернативно, вы можете передать задачу непосредственно в следующий поток и позволить ему вернуть задачу на карту вместо этого.

+0

Итак, вы рекомендуете, чтобы я реализовал свой собственный ReadWriteLock, используя что-то вроде ConcurrentMap? Я не уверен, что было бы проще/лучше, чем использование AbstractQueuedSynchronizer. Я должен был бы реализовать ожидание и поставить себя в очередь. –

+0

@AndrewMcKinlay: Нет, я подразумеваю, что вам может не понадобиться ReadWriteLock. Если все, что вы хотите, обрабатывает некоторую задачу, используя несколько потоков, которые выполняют некоторую другую работу над задачей, мое предложение - это один из способов сделать это. В общем, вы используете ReadWriteLock, чтобы получить прирост производительности, когда есть много читателей по сравнению с писателями. На самом деле можно предположить, что ReadWriteLock в моей предлагаемой реализации (вместо того, чтобы блокировать объект, как описано в моем ответе), но это оптимизация и не является фундаментальной. –

+0

Мне нужно/нужно несколько считывателей/одиночных писателей в качестве ReadWriteLock. –

1

Я знаю, что вы уже приняли другой ответ. Но я все еще думаю, что вы собираетесь создать для себя совершенно кошмар. В конце концов, клиент не сможет вернуться и освободить эти разрешения, и вы начнете задаваться вопросом, почему «писатель» никогда не пишет.

Если бы я делал это, я бы сделал это так:

Client issues a request to start a transaction 
The initial request creates a task (Runnable/Callable) and places it in an Executor for execution 
The initial request also registers that task in a Map by transaction id 

Client issues the second request to close the transaction 
The close request finds the task by transaction id in a map 
The close request calls a method on the task to indicate that it should close (probably a signal on a Condition or if data needs to be passed, placing an object in a BlockingQueue) 

Теперь задача транзакции будет иметь такой код:

public void run() { 
    readWriteLock.readLock().lock(); 
    try { 
     //do stuff for initializing this transaction 
     if (condition.await(someDurationAsLong, someTimeUnit)({ 
      //do the rest of the transaction stuff 
     } else { 
      //do some other stuff to back out the transaction 
     } 
    } finally { 
     readWriteLock.readLock.unlock(); 
    } 
} 
+0

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

+0

Программное обеспечение, над которым я работаю, находится в производстве в 100-х годах установки, и я видел такое злоупотребление, о котором вы меня предупреждаете, и я хорошо знаю, что с ним нужно защищаться. Не поймите меня неправильно, я не новичок, но я также далек от совершенства. Я уверен, что некоторые из моих проектных решений не являются оптимальными. Если вас это интересует, вы можете увидеть больше того, что я нахожу в своем блоге - http://thesoftwarelife.blogspot.ca –

+0

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

0

Они, кажется, опустили мяч на это одно из устаревших com.sun.corba.se.impl.orbutil.concurrent.Mutex;

Я имею в виду, кто в здравом уме думает, что нам не понадобятся не-реентерабельные замки. Здесь мы находимся, тратя время на то, чтобы обсудить определение реентерабера (может незначительное изменение значения для каждого фрейма кстати). Да, я хочу tryLock в том же потоке, что это плохо? он не зайдет в тупик, потому что иначе не будет. Блокировка без реентера, которая блокируется в одном потоке, может быть очень полезна для предотвращения ошибок в приложениях с графическим интерфейсом, где пользователь нажимает одну и ту же кнопку быстро и многократно. Был там, сделал это, QT был прав ... снова.

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