首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >能够检测某些类型的编程错误的互斥对象。

能够检测某些类型的编程错误的互斥对象。
EN

Code Review用户
提问于 2022-11-15 16:42:57
回答 1查看 93关注 0票数 3

此互斥阻止对目标资源的并发访问。一个关键的要求是在(调度程序)线程中获得并在另一个(worker)线程中释放的能力。不幸的是,这意味着不能使用广泛使用的原语,如隐式锁(synchronized)或来自java.util.concurrentLock,因为它们不允许获取锁的线程将其传递给其他线程。

当互斥锁被保存在一个长期存在的变量(甚至一个实例字段)并且方法.release()不止一次被调用时,这一关键需求可能会导致编程错误。我们希望这样的编程错误能够抛出特定的运行时异常,而不是默默地允许不正确的程序执行。

表现并不重要。此锁通常不会被两个或多个线程争用。正确性是必需的,可读性很高。

API取自Semaphore (这个类可以看作是一个Semaphore,只有1许可,不允许许可数量变为负数):

  • acquire()
  • acquire(long timeout, TimeUnit timeUnit)
  • tryAcquire()
  • tryAcquire(long timeout, TimeUnit timeUnit)
  • release(Combination combination)

客户端代码中的使用模式:

代码语言:javascript
复制
Combination combination = lock.acquire();
threadPool.submit(() -> {
   try { ... } finally {
       lock.release(combination);
   }
});

请注意,每次获得锁时,都会生成新的Combination并将其返回给调用方。只有使用完全相同的release()调用Combination才能使锁再次可用。一旦释放,内部组合将被清除(设置为null),并且锁被解锁。

代码语言:javascript
复制
public class CombinationLock {

    // This lock is used to synchronize access to the combination field
    private final Lock lock = new ReentrantLock();
    private final Condition lockReleased = lock.newCondition();

    // Initially null, this field is set to a non-null value from acquire/tryAcquire methods
    // and then set to null again from release().
    private Combination combination;

    /**
     * Block the caller thread indefinitely until the lock is acquired
     *
     * @return the combination for releasing the lock
     * @throws InterruptedException if waiting thread is interrupted
     */
    public Combination acquire() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            while (combination != null) {
                lockReleased.await();
            }
            combination = Combination.generate();
            return combination;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Wait for the lock to be available. If available at the time of invocation, lock and return the
     * combination to release it later, otherwise just return null.
     *
     * @return null, or the combination for releasing the lock
     */
    public Combination tryAcquire() throws InterruptedException {
        boolean locked = lock.tryLock();
        if (locked) {
            try {
                if (combination == null) {
                    combination = Combination.generate();
                    return combination;
                } else {
                    return null;
                }
            } finally {
                lock.unlock();
            }
        } else {
            return null;
        }
    }

    /**
     * Wait for the given timeout, otherwise return null
     *
     * @param timeout in arbitrary unit
     * @param unit the timeout unit
     * @return NULL, of the combination to release the lock
     * @throws InterruptedException if thread is interrupted
     */
    public Combination tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {
        Stopwatch stopwatch =  Stopwatch.createStarted();
        boolean locked = lock.tryLock(timeout, unit);
        if (locked) {
            try {
                while (combination != null) {
                    long remainingWaitTimeNanos = unit.toNanos(timeout) - stopwatch.elapsed(TimeUnit.NANOSECONDS);
                    boolean timeRunOut = lockReleased.await(remainingWaitTimeNanos, TimeUnit.NANOSECONDS);
                    if (timeRunOut) {
                        return null;
                    }
                }
                combination = Combination.generate();
                return combination;
            } finally {
                lock.unlock();
            }
        } else {
            return null;
        }
    }

    public boolean isLocked() {
        lock.lock();
        try {
            return combination != null;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Release the lock
     *
     * @param combination previously generated from acquire/tryAcquire
     */
    public void release(Combination combination) {
        Preconditions.checkNotNull(combination, "Given combination must be non-null. Did you forget to check [combination != null] after calling tryAcquire()?");

        lock.lock();
        try {
            if (!isLocked()) {
                throw new RuntimeException("This lock is not locked");
            }

            if (this.combination.equals(combination)) {
                this.combination = null;
                lockReleased.signalAll();
            } else {
                throw new RuntimeException("Someone else acquired the lock since you got your combination so you must have released it");
            }
        } finally {
            lock.unlock();
        }
    }
}
EN

回答 1

Code Review用户

发布于 2022-11-15 21:49:39

欢迎,并发很难!你测试过这个代码了吗?你有单元测试吗?

  • 此外,公共isLocked方法似乎毫无用处。它有什么意义?它是锁的?如果您在这么短的时间内获得锁(仅仅是为了获取boolean,则方法中的信息是无用的,因为它可能很快就过时了。如果你想对它作出反应,你想用原子的方式来做。另一种方法是从它中移除锁,因为在这种情况下,它并没有真正对您有任何帮助。
  • 我会小心返回nulls (即使您的示例用法也会在acquire失败时抛出NullPointerException!)在acquire方法中,考虑使用Optional或抛出异常。
  • 为了确保lock总是被锁定或解锁,较少容易出错的方法有一些基本的方法框架,这是为您做的,然后在“中间”执行您的功能块。然后,您的原子关键部分是这些lambda,它们与同步代码分离,同步代码位于一个地方。代码更简洁,更容易出错。
  • 我认为Combination类的存在没有任何意义。是为了确保不发布不正确的CombinationLock实例吗?
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/281234

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档