【问题标题】:Erratic StampedLock.unlock(long) behaviour?不稳定的 StampedLock.unlock(long) 行为?
【发布时间】:2015-08-19 06:46:25
【问题描述】:

我正面临关于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());

奇怪的行为是关于解锁如何“容忍”错误的读取戳记。你觉得它正确吗?


完整代码供参考:

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

【问题讨论】:

  • 代码应该发布在问题中。
  • 不能,因为 SO 拒绝发布我的 q°,因为代码代表了太多文本
  • 我已经添加了 MCVE,但我也想要关于整个测试用例(readLock、unlock、writeLock、re-unlock、write2read 转换和最终解锁)行为的反馈
  • 查看 OpenJDK 中的 StampedLock 源代码,我看不到它存储“有效”戳记。它只检查邮票是否在有效范围内。对于读取锁,介于 257(100000001b) 和 383(101111111) 之间的任何内容都有效,直到有超过 127 个读取器(然后溢出机制启动)。 hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/share/classes/…
  • 您的问题有背景吗?这在实践中是如何发生的?

标签: java multithreading concurrency locking java-8


【解决方案1】:

简答:

在图章中添加两个正在修改其中不需要在读取模式锁中进行验证的部分。

长答案:

邮票包含两条信息:一个状态序列号,以及有多少读者。状态编号存储在标记的前 57 位中,阅读器计数存储在后 7 位中。因此,当您将 2 添加到图章时,您将阅读器计数从 1 更改为 3,并且保持状态编号不变。由于仅在读取模式下获取了 StampedLock,因此仅验证状态编号并忽略读取器计数。这是有道理的,因为读锁应该能够以任何顺序解锁。

例如:从现有的 StampedLock 获取读取标记,状态编号为 4,读取器计数为 1。第二个读取标记从相同的 StampedLock 获取,状态编号为 4,读取器计数of 2. 请注意,邮票的状态编号是相同的,因为在获得邮票之间 StampedLock 的状态没有改变。第一个读取标记用于解锁。第一个标记 (4) 的状态编号与 StampedLock (4) 的状态编号匹配,所以没关系。第一个标记 (1) 的读取器计数与 StampedLock (2) 的读取器计数不匹配,但这没关系,因为读取锁应该能够以任何顺序解锁。所以解锁成功。

请注意,StampedLocks were designed to be high-performing read/write locks 用于内部实用程序,不能承受恶意编码,因此它在其预期范围内运行。我确实认为 unlock() 的 Javadoc 具有误导性。

【讨论】:

【解决方案2】:

javadocs 中的关键部分:

邮票使用有限的表示,并且在密码学上不安全(即,有效的邮票可能是可猜测的)。

这意味着您应该将它们视为不透明的值,而不是尝试以任何方式修改它们。

可能是可猜到的本质上就是您的 -1、+2、+4 算术所做的。如果您有一个很好的猜测起点(例如以前的令牌),这不仅是可猜测的,而且很容易做到。

另外,StampedLock.validate(long) 声明:

使用不是从 tryOptimisticRead() 获得的值或此锁的锁定方法调用此方法没有定义的效果或结果。

换句话说:任何不直接从 Lock 的方法之一获得的令牌值不仅无效,而且还包含未定义的行为。

【讨论】:

    猜你喜欢
    • 2011-01-27
    • 2019-08-02
    • 1970-01-01
    • 2012-04-24
    • 2011-04-24
    • 2020-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多