StampedLock.unlock(long) 行为不稳定?
Erratic StampedLock.unlock(long) behaviour?
我遇到了关于 StampedLock 的奇怪行为。以下是主要有问题的代码行:
StampedLock lock = new StampedLock();
long stamp1 = lock.readLock();
System.out.printf("Read lock count: %d%n", lock.getReadLockCount());
lock.unlock(stamp1 + 2);
System.out.printf("Read lock count: %d%n", lock.getReadLockCount());
奇怪的行为是关于如何解锁 "tolerates" 错误的读取标记。你觉得正确吗?
完整代码供参考:
public class StampedLockExample {
static StampedLock lock = new StampedLock();
static void println(String message, Object... args) {
System.out.printf(message, args);
System.out.println();
}
static void printReadLockCount() {
println("Lock count=%d", lock.getReadLockCount());
}
static long tryReadLock() {
long stamp = lock.tryReadLock();
println("Gets read lock (%d)", stamp);
printReadLockCount();
return stamp;
}
static long tryWriteLock() {
long stamp = lock.tryWriteLock();
println("Gets write lock (%d)", stamp);
return stamp;
}
static long tryConvertToReadLock(long stamp) {
long newOne = lock.tryConvertToReadLock(stamp);
println("Gets read lock (%d -> %d)", stamp, newOne);
printReadLockCount();
return newOne;
}
static void tryUnlock(long stamp) {
try {
lock.unlock(stamp);
println("Unlock (%d) successfully", stamp);
} catch (IllegalMonitorStateException e) {
println("Unlock (%d) failed", stamp);
}
printReadLockCount();
}
public static void main(String[] args) {
println("%n--- Gets two read locks ---");
long stamp1 = tryReadLock();
long stamp2 = tryReadLock();
long min = Math.min(stamp1, stamp2);
long max = Math.max(stamp1, stamp2);
println("%n--- Tries unlock (-1 / +2 / +4) ---");
tryUnlock(min - 1);
tryUnlock(max + 2);
tryUnlock(max + 4);
println("%n--- Gets write lock ---");
long stamp3 = tryWriteLock();
println("%n--- Tries unlock (-1 / +1) ---");
tryUnlock(stamp3 - 1);
tryUnlock(stamp3 + 1);
println("%n--- Tries write > read conversion ---");
long stamp4 = tryConvertToReadLock(stamp3);
println("%n--- Tries unlock last write stamp (-1 / 0 / +1) ---");
tryUnlock(stamp3 - 1);
tryUnlock(stamp3);
tryUnlock(stamp3 + 1);
println("%n--- Tries unlock (-1 / +1) ---");
tryUnlock(stamp4 - 1);
tryUnlock(stamp4 + 1);
}
}
输出:
--- Gets two read locks ---
Gets read lock (257)
Lock count=1
Gets read lock (258)
Lock count=2
--- Tries unlock (-1 / +2 / +4) ---
Unlock (256) failed
Lock count=2
Unlock (260) successfully
Lock count=1
Unlock (262) successfully
Lock count=0
--- Gets write lock ---
Gets write lock (384)
--- Tries unlock (-1 / +1) ---
Unlock (383) failed
Lock count=0
Unlock (385) failed
Lock count=0
--- Tries write > read conversion ---
Gets read lock (384 -> 513)
Lock count=1
--- Tries unlock last write stamp (-1 / 0 / +1) ---
Unlock (383) failed
Lock count=1
Unlock (384) failed
Lock count=1
Unlock (385) failed
Lock count=1
--- Tries unlock (-1 / +1) ---
Unlock (512) failed
Lock count=1
Unlock (514) successfully
Lock count=0
简答:
向戳记添加两个是修改其中不需要在读取模式锁中验证的部分。
长答案:
邮票包含两条信息:状态序号,以及有多少个reader。 stamp 的前 57 位存储状态编号,后 7 位存储 reader 计数。因此,当您将 2 添加到标记时,您将 reader 计数从 1 更改为 3,并保持状态编号不变。由于 StampedLock 仅在读取模式下获取,因此仅验证状态编号而忽略 reader 计数。这是有道理的,因为读锁应该能够以任何顺序解锁。
例如:读取戳记是从现有的 StampedLock 获取的,状态号为 4,reader 计数为 1。第二个读取戳记是从同一个 StampedLock 获取的,状态号为4 和 reader 计数 2。请注意,图章的状态编号是相同的,因为 StampedLock 的状态在两次获取图章之间没有改变。第一个读取标记用于解锁。第一个图章 (4) 的州编号与 StampedLock (4) 的州编号相匹配,所以没问题。第一个标记 (1) 的 reader 计数与 StampedLock (2) 的 reader 计数不匹配,但这无关紧要,因为读锁应该能够以任何顺序解锁。至此解锁成功
请注意,StampedLocks were designed to be high-performing read/write locks for internal utilities, not something to withstand malicious coding, so it is operating within its intended boundaries. I do think the Javadoc of unlock() 具有误导性。
javadocs 中的关键部分:
Stamps use finite representations, and are not cryptographically secure (i.e., a valid stamp may be guessable).
这意味着您应该将它们视为不透明值,不要尝试以任何方式修改它们。
可能是可以猜到的 本质上就是您的 -1、+2、+4 算法所做的。如果你有一个很好的猜测起点,比如以前的标记,这不仅是可以猜测的,而且很容易做到。
此外,StampedLock.validate(long) 指出:
Invoking this method with a value not obtained from tryOptimisticRead() or a locking method for this lock has no defined effect or result.
换句话说:任何不是直接从 Lock 的方法之一获得的令牌值不仅是无效的,而且会导致未定义的行为。
我遇到了关于 StampedLock 的奇怪行为。以下是主要有问题的代码行:
StampedLock lock = new StampedLock();
long stamp1 = lock.readLock();
System.out.printf("Read lock count: %d%n", lock.getReadLockCount());
lock.unlock(stamp1 + 2);
System.out.printf("Read lock count: %d%n", lock.getReadLockCount());
奇怪的行为是关于如何解锁 "tolerates" 错误的读取标记。你觉得正确吗?
完整代码供参考:
public class StampedLockExample {
static StampedLock lock = new StampedLock();
static void println(String message, Object... args) {
System.out.printf(message, args);
System.out.println();
}
static void printReadLockCount() {
println("Lock count=%d", lock.getReadLockCount());
}
static long tryReadLock() {
long stamp = lock.tryReadLock();
println("Gets read lock (%d)", stamp);
printReadLockCount();
return stamp;
}
static long tryWriteLock() {
long stamp = lock.tryWriteLock();
println("Gets write lock (%d)", stamp);
return stamp;
}
static long tryConvertToReadLock(long stamp) {
long newOne = lock.tryConvertToReadLock(stamp);
println("Gets read lock (%d -> %d)", stamp, newOne);
printReadLockCount();
return newOne;
}
static void tryUnlock(long stamp) {
try {
lock.unlock(stamp);
println("Unlock (%d) successfully", stamp);
} catch (IllegalMonitorStateException e) {
println("Unlock (%d) failed", stamp);
}
printReadLockCount();
}
public static void main(String[] args) {
println("%n--- Gets two read locks ---");
long stamp1 = tryReadLock();
long stamp2 = tryReadLock();
long min = Math.min(stamp1, stamp2);
long max = Math.max(stamp1, stamp2);
println("%n--- Tries unlock (-1 / +2 / +4) ---");
tryUnlock(min - 1);
tryUnlock(max + 2);
tryUnlock(max + 4);
println("%n--- Gets write lock ---");
long stamp3 = tryWriteLock();
println("%n--- Tries unlock (-1 / +1) ---");
tryUnlock(stamp3 - 1);
tryUnlock(stamp3 + 1);
println("%n--- Tries write > read conversion ---");
long stamp4 = tryConvertToReadLock(stamp3);
println("%n--- Tries unlock last write stamp (-1 / 0 / +1) ---");
tryUnlock(stamp3 - 1);
tryUnlock(stamp3);
tryUnlock(stamp3 + 1);
println("%n--- Tries unlock (-1 / +1) ---");
tryUnlock(stamp4 - 1);
tryUnlock(stamp4 + 1);
}
}
输出:
--- Gets two read locks ---
Gets read lock (257)
Lock count=1
Gets read lock (258)
Lock count=2
--- Tries unlock (-1 / +2 / +4) ---
Unlock (256) failed
Lock count=2
Unlock (260) successfully
Lock count=1
Unlock (262) successfully
Lock count=0
--- Gets write lock ---
Gets write lock (384)
--- Tries unlock (-1 / +1) ---
Unlock (383) failed
Lock count=0
Unlock (385) failed
Lock count=0
--- Tries write > read conversion ---
Gets read lock (384 -> 513)
Lock count=1
--- Tries unlock last write stamp (-1 / 0 / +1) ---
Unlock (383) failed
Lock count=1
Unlock (384) failed
Lock count=1
Unlock (385) failed
Lock count=1
--- Tries unlock (-1 / +1) ---
Unlock (512) failed
Lock count=1
Unlock (514) successfully
Lock count=0
简答:
向戳记添加两个是修改其中不需要在读取模式锁中验证的部分。
长答案:
邮票包含两条信息:状态序号,以及有多少个reader。 stamp 的前 57 位存储状态编号,后 7 位存储 reader 计数。因此,当您将 2 添加到标记时,您将 reader 计数从 1 更改为 3,并保持状态编号不变。由于 StampedLock 仅在读取模式下获取,因此仅验证状态编号而忽略 reader 计数。这是有道理的,因为读锁应该能够以任何顺序解锁。
例如:读取戳记是从现有的 StampedLock 获取的,状态号为 4,reader 计数为 1。第二个读取戳记是从同一个 StampedLock 获取的,状态号为4 和 reader 计数 2。请注意,图章的状态编号是相同的,因为 StampedLock 的状态在两次获取图章之间没有改变。第一个读取标记用于解锁。第一个图章 (4) 的州编号与 StampedLock (4) 的州编号相匹配,所以没问题。第一个标记 (1) 的 reader 计数与 StampedLock (2) 的 reader 计数不匹配,但这无关紧要,因为读锁应该能够以任何顺序解锁。至此解锁成功
请注意,StampedLocks were designed to be high-performing read/write locks for internal utilities, not something to withstand malicious coding, so it is operating within its intended boundaries. I do think the Javadoc of unlock() 具有误导性。
javadocs 中的关键部分:
Stamps use finite representations, and are not cryptographically secure (i.e., a valid stamp may be guessable).
这意味着您应该将它们视为不透明值,不要尝试以任何方式修改它们。
可能是可以猜到的 本质上就是您的 -1、+2、+4 算法所做的。如果你有一个很好的猜测起点,比如以前的标记,这不仅是可以猜测的,而且很容易做到。
此外,StampedLock.validate(long) 指出:
Invoking this method with a value not obtained from tryOptimisticRead() or a locking method for this lock has no defined effect or result.
换句话说:任何不是直接从 Lock 的方法之一获得的令牌值不仅是无效的,而且会导致未定义的行为。