Есть ли не реентерабельный ReadWriteLock, который я могу использовать?

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

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

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

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

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

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

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

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

Ответы

Ответ 1

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

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);
}

Ответ 2

Я не уверен, что понимаю вопрос. Не-реентерабельный ReadWriteLock является оксюморонным ИМО, который означает, что слово "reentrant" имеет специфическое значение в народном языке. Он должен использоваться из нескольких потоков, иначе он не будет работать. Существует только 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

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

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

Ответ 4

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

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

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();
    }
}

Ответ 5

Кажется, что они сбросили мяч на этот, опуская com.sun.corba.se.impl.orbutil.concurrent.Mutex;

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