有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java被关于ReentrantReadWriteLock#tryLock失败的jcstress测试弄糊涂了

我正在努力克服压力。为了确保我理解它,我决定为一些我知道必须正确的东西编写一些简单的测试:java.util.concurrent.locks.ReentrantReadWriteLock

我编写了一些非常简单的测试来检查锁模式兼容性。不幸的是,有两项压力测试失败:

  1. X_S

    true, true        32,768     FORBIDDEN  No default case provided, assume FORBIDDEN
    
  2. X_X

    true, true        32,767     FORBIDDEN  No default case provided, assume FORBIDDEN
    

在我看来,一个线程不应该拥有读锁,而另一个线程也拥有写锁。同样,两个线程不可能同时持有写锁

我意识到问题可能不在ReentrantReadWriteLock。我想我可能在jcmstress测试中犯了一些愚蠢的错误,比如JMM和读取锁的状态

不幸的是,我无法发现这个问题。谁能帮我理解这个(愚蠢的?)我犯的错误

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.ZZ_Result;

import java.util.concurrent.locks.ReentrantReadWriteLock;

/*
 * |-----------------|
 * |  COMPATIBILITY  |
 * |-----------------|
 * |     | S   | X   |
 * |-----------------|
 * | S   | YES | NO  |
 * | X   | NO  | NO  |
 * |-----------------|
 */
public class ReentrantReadWriteLockBooleanCompatibilityTest {

    @State
    public static class S {
        public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

        public boolean shared() {
            return lock.readLock().tryLock();
        }

        public boolean exclusive() {
            return lock.writeLock().tryLock();
        }
    }

    @JCStressTest
    @Outcome(id = "true, true", expect = Expect.ACCEPTABLE, desc = "T1 and T2 are both acquired S")
    public static class S_S {
        @Actor
        public void actor1(S s, ZZ_Result r) { r.r1 = s.shared(); }
        @Actor
        public void actor2(S s, ZZ_Result r) { r.r2 = s.shared(); }
    }

    @JCStressTest
    @Outcome(id = "true, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired S, and T2 could not acquire X")
    @Outcome(id = "false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired X, and T1 could not acquire S")
    public static class S_X {
        @Actor
        public void actor1(S s, ZZ_Result r) { r.r1 = s.shared(); }
        @Actor
        public void actor2(S s, ZZ_Result r) { r.r2 = s.exclusive(); }
    }

    @JCStressTest
    @Outcome(id = "true, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire S")
    @Outcome(id = "false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired S and T1 could not acquire X")
    public static class X_S {
        @Actor
        public void actor1(S s, ZZ_Result r) { r.r1 = s.exclusive(); }
        @Actor
        public void actor2(S s, ZZ_Result r) { r.r2 = s.shared(); }
    }

    @JCStressTest
    @Outcome(id = "true, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire X")
    @Outcome(id = "false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired X and T1 could not acquire X")
    public static class X_X {
        @Actor
        public void actor1(S s, ZZ_Result r) { r.r1 = s.exclusive(); }
        @Actor
        public void actor2(S s, ZZ_Result r) { r.r2 = s.exclusive(); }
    }
}

我确实试着在jcstress-dev上询问过这件事,但从未收到回复-http://mail.openjdk.java.net/pipermail/jcstress-dev/2018-August/000346.html。很抱歉交叉发布,但我需要帮助,所以我将转载到StackOverflow,希望能引起更多观众的注意


共 (1) 个答案

  1. # 1 楼答案

    在JC0.3下运行时,测试通过。在版本0.4中,行为改变为包括启动时运行的健全性检查的结果(参见针对错误的jcstress omits samples gathered during sanity checks

    一些健全性检查在单个线程中运行,并且您的测试不会处理两个参与者都被同一线程调用的情况;您正在测试一个可重入锁,因此如果已经持有写锁,则读锁将通过

    这可以说是jcstress中的一个bug,因为@Actor上的文档说不变量是:

    • Each method is called only by one particular thread.
    • Each method is called exactly once per State instance.

    虽然文档的措辞不是很清楚,但生成的源代码清楚地表明,目的是在自己的线程中运行每个参与者

    解决此问题的一种方法是允许单螺纹壳体通过:

    @State
    public static class S {
        public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
        public boolean shared() {
            return lock.readLock().tryLock();
        }
    
        public boolean exclusive() {
            return lock.writeLock().tryLock();
        }
    
        public boolean locked() {
            return lock.isWriteLockedByCurrentThread();
        }
    }
    
    @JCStressTest
    @Outcome(id = "true, false, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire S")
    @Outcome(id = "false, false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired S and T1 could not acquire X")
    @Outcome(id = "true, true, true", expect = Expect.ACCEPTABLE, desc = "T1 acquired X and then acquired S")
    public static class X_S {
        @Actor
        public void actor1(S s, ZZZ_Result r) {
            r.r1 = s.exclusive();
        }
        @Actor
        public void actor2(S s, ZZZ_Result r) {
            r.r2 = s.locked();
            r.r3 = s.shared();
        }
    }
    

    或者检查单螺纹外壳,并将其标记为“有趣”而不是“接受”:

    @State
    public static class S {
        public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public AtomicReference<Thread> firstThread = new AtomicReference<>();
    
        public boolean shared() {
            firstThread.compareAndSet(null, Thread.currentThread());
            return lock.readLock().tryLock();
        }
    
        public boolean exclusive() {
            firstThread.compareAndSet(null, Thread.currentThread());
            return lock.writeLock().tryLock();
        }
    
        public boolean sameThread() {
            return Thread.currentThread().equals(firstThread.get());
        }
    
        public boolean locked() {
            return lock.isWriteLockedByCurrentThread();
        }
    }
    
    @JCStressTest
    @Outcome(id = "false, true, false, false", expect = Expect.ACCEPTABLE, desc = "T1 acquired X, and T2 could not acquire X")
    @Outcome(id = "false, false, false, true", expect = Expect.ACCEPTABLE, desc = "T2 acquired X and T1 could not acquire X")
    @Outcome(id = "false, true, true, true", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Both actors ran in the same thread!")
    @Outcome(id = "true, true, false, true", expect = Expect.ACCEPTABLE_INTERESTING, desc = "Both actors ran in the same thread!")
    public static class X_X {
        @Actor
        public void actor1(S s, ZZZZ_Result r) {
            r.r1 = s.sameThread();
            r.r2 = s.exclusive();
        }
        @Actor
        public void actor2(S s, ZZZZ_Result r) {
            r.r3 = s.sameThread();
            r.r4 = s.exclusive();
        }
    }
    

    正如您在评论中所指出的,上述测试中的最终@Outcome从未发生过。这是因为单线程健全性检查在运行参与者之前不会对其进行洗牌(请参阅生成的测试类上的方法sanityCheck_Footprints