Почему java .util.concurrent.locks.AbstractOwnableSynchronizer.exclusiveOwnerThread не объявлен как volatile? - PullRequest
0 голосов
/ 23 марта 2020

При чтении исходного кода java.util.concurrent.locks.ReentrantLock я обнаружил, что метод tryLock() реализован следующим образом:

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

Мы пытаемся «завладеть» блокировкой или проверить, владеем ли мы уже блокировка, в зависимости от state поддерживается в AbstractQueuedSynchronizer. Но мне интересно, почему переменная state объявлена ​​как volatile , а переменная exclusiveOwnerThread нет? Спасибо!

1 Ответ

0 голосов
/ 24 марта 2020

Чтобы понять, почему exclusiveOwnerThread не нужно быть волатильным, полезно взглянуть на методы получения и выпуска вместе.

Метод получения 1 :

/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

Метод выпуска:

@ReservedStackAccess
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

Также важно понимать, что exclusiveOwnerThread не ссылается на какой-либо произвольный объект, не имеющий отношения к вовлеченным потокам. В частности, он содержит ссылку на экземпляр Thread и строго сравнивается с вызывающим потоком. Другими словами, имеет значение, если:

Thread.currentThread() == getExclusiveOwnerThread()

Что будет истинно, если и только если вызывающий поток ранее вызвал #setExclusiveOwnerThread(Thread), с самим собой в качестве аргумента, из-за объединенной природы #nonfairTryAcquire(int) и #tryRelease(int). Действия в одном потоке всегда происходят до последующие действия в том же потоке.

Так что если c != 0, то есть два сценария ios:

  1. Вызывающему потоку принадлежит синхронизатор.

    • Поскольку действия в одном потоке всегда произойдет-до последующие действия в том же потоке гарантированно getExclusiveOwnerThread() вернут ссылку на вызывающий поток.
  2. вызывающий поток не не владеет синхронизатором.

    • Больше не имеет значения, какая ссылка возвращается getExclusiveOwnerThread(), поскольку для этого невозможно метод для возврата ссылки на вызывающий поток.

      Вызывающий поток никогда не сможет увидеть устаревшую ссылку на себя из-за вызова setExclusiveOwnerThread(null) в #tryRelease(int). Это означает, что getExclusiveOwnerThread() может возвращать null или какую-либо другую ссылку Thread (устаревшую или нет), но не ссылку на вызывающий поток.

Причина, по которой state должен быть изменчивым, заключается в том, что он распределяется между потоками таким образом, что первостепенно каждый поток видит самое последнее значение.


1. Реализация FairSync#tryAcquire(int) имеет почти такую ​​же реализацию, за исключением того, что учитывает порядок вызова потоков.

...