TL; DR: блокировки записи появляются в списке "ownable синхронизаторов" блокировки чтения не.
В итоге у меня появился следующий MVCE, чтобы попытаться понять, что такое «управляемый синхронизатор». Идея состояла в том, чтобы блокировать/разблокировать блокировки/разблокировки повторных попыток чтения/записи и видеть влияние на разные дампы потоков при разных таймингах (взятых в jVisualVM, в то время как проект Eclipse был приостановлен в точках прерывания в определенных строках).
Вот код (в пакете "замок"):
public class LockTest {
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
public static void main(String[] args) {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName()+": read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount());
new Th().start();
synchronized (LockTest.class) {
try { LockTest.class.wait(); } catch (InterruptedException e) { }
}
lock.readLock().unlock();
System.out.println(Thread.currentThread().getName()+": unlocked read lock. Read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount()+". Getting write lock");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+": got write lock. Unlocking (=>Thread dump #3)"); // Take thead dump #3 here ("main" has a write lock, "other" has died)
lock.writeLock().unlock();
}
static class Th extends Thread {
Th() { super("other"); }
public void run() {
System.out.println(Thread.currentThread().getName()+": read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount());
if (!lock.writeLock().tryLock())
System.out.println(Thread.currentThread().getName()+": cannot lock write");
else {
System.out.println(Thread.currentThread().getName()+": lock write taken");
lock.writeLock().unlock();
}
System.out.println(Thread.currentThread().getName()+": trying to unlock read lock");
try {
lock.readLock().unlock();
System.out.println(Thread.currentThread().getName()+": successfully unlocked read lock. Read hold "+lock.getReadHoldCount()+" read lock "+lock.getReadLockCount());
} catch (IllegalMonitorStateException e) {
System.out.println(Thread.currentThread().getName()+": cannot unlock read lock: "+e.getMessage());
}
synchronized (LockTest.class) {
System.out.println(Thread.currentThread().getName()+": notifying write lock take (=>Thread dump #1)");
LockTest.class.notify(); // Take thead dump #1 here ("main" has a read lock)
}
System.out.println(Thread.currentThread().getName()+": locking write lock");
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+": unlocking write lock (=>Thread dump #2)"); // Take thead dump #2 here ("other" has a write lock)
lock.writeLock().unlock();
}
}
}
Вот результат:
main: read hold 1 read lock 1
other: read hold 0 read lock 1
other: cannot lock write
other: trying to unlock read lock
other: cannot unlock read lock: attempt to unlock read lock, not locked by current thread
other: notifying write lock take (=>Thread dump #1)
other: locking write lock
main: unlocked read lock. Read hold 0 read lock 0. Getting write lock
other: unlocking write lock (=>Thread dump #2)
main: got write lock. Unlocking (=>Thread dump #3)
Теперь нить свалки.
Сброс резьбы # 1 берется, когда резьба "main" получает блокировку чтения. Как мы можем видеть, нет «ownable синхронизатор» не принадлежит резьбе:
"main" prio=10 tid=0x00007fea5c00d000 nid=0x1866 in Object.wait() [0x00007fea65bd5000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007acf62620> (a java.lang.Class for lock.LockTest)
at java.lang.Object.wait(Object.java:503)
at lock.LockTest.main(LockTest.java:14)
- locked <0x00000007acf62620> (a java.lang.Class for lock.LockTest)
Locked ownable synchronizers:
- None
"other" prio=10 tid=0x00007fea5c0e0800 nid=0x1883 at breakpoint[0x00007fea3abe8000]
java.lang.Thread.State: RUNNABLE
at lock.LockTest$Th.run(LockTest.java:46)
- locked <0x00000007acf62620> (a java.lang.Class for lock.LockTest)
Locked ownable synchronizers:
- None
Thread свалка # 2 принимается после того, как нить «другой» взял блокировку записи. Оказывается, в «ownable синхронизаторов»:
"main" prio=10 tid=0x00007fea5c00d000 nid=0x1866 waiting on condition [0x00007fea65bd5000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007acf63278> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:834)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:867)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1197)
at java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.lock(ReentrantReadWriteLock.java:945)
at lock.LockTest.main(LockTest.java:18)
Locked ownable synchronizers:
- None
"other" prio=10 tid=0x00007fea5c0e0800 nid=0x1883 at breakpoint[0x00007fea3abe8000]
java.lang.Thread.State: RUNNABLE
at lock.LockTest$Th.run(LockTest.java:51)
Locked ownable synchronizers:
- <0x00000007acf63278> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)
Thread свалка # 3 принимается после того, как нити «другой» выпустила блокировку записи (и умер), и нити «основной» взял его:
"main" prio=10 tid=0x00007fea5c00d000 nid=0x1866 at breakpoint[0x00007fea65bd5000]
java.lang.Thread.State: RUNNABLE
at lock.LockTest.main(LockTest.java:19)
Locked ownable synchronizers:
- <0x00000007acf63278> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)
Таким образом, блокировки записи появятся в списке «заблокированных управляемых синхронизаторов», когда блокировки чтения не будут. Несмотря на то, что getReadHoldCount()
показывает количество блокировок чтения, выполненных текущим потоком, чтение «блокировка», похоже, не относится к определенному потоку и поэтому отсутствует в списке. И это затрудняет отладку блокировок (или, скажем, «не так просто, как с помощью jVisualVM»).
EDIT: Для того, чтобы помочь выяснить, копирование/ошибку вставки с замками, принятых и не выпустили, например, в:
myLock.readLock().lock();
try {
// ...
} finally {
myLock.readLock().lock(); // Oops! Should be "unlock()"
}
вы можете использовать следующую командную строку Linux в корне каталога источника:
find . -name '*.java' -exec grep -Hn 'myLock.readLock().lock();' {} \; | wc -l
покажет, сколько чтения замки приняты, а также:
find . -name '*.java' -exec grep -Hn 'myLock.readLock().unlock();' {} \; | wc -l
отображает, сколько считываемых замков выпущено. Если числа не совпадают, удалите | wc -l
, чтобы показать подробную информацию о имени файла (grep -H
) и номер строки (grep -n
).
Хорошая работа! Спасибо, что поделились –
Я еще не приму своего ответа, если кто-нибудь придумает более подробное объяснение. – Matthieu
Замечательный анализ. Выделите резюме/заключение либо в начале, либо в конце. –