Por que a atualização ReadWriteLock não é permitida?

9
O downgrade

ReadWriteLock é permitido pela implementação ReentrantReadWriteLock ( tryLock() do exemplo abaixo sempre retorna true ):

void downgrade(final ReadWriteLock readWriteLock) {
    boolean downgraded = false;
    readWriteLock.writeLock().lock();
    try {
        // Always true, as we already hold a W lock.
        final boolean readLockAcquired = readWriteLock.readLock().tryLock();
        if (readLockAcquired) {
            // Now holding both a R and a W lock.
            assert ((ReentrantReadWriteLock) readWriteLock).getReadHoldCount() == 1;
            assert ((ReentrantReadWriteLock) readWriteLock).getWriteHoldCount() == 1;

            readWriteLock.writeLock().unlock();
            downgraded = true;
            try {
                // Now do some work with only a R lock held
            } finally {
                readWriteLock.readLock().unlock();

                assert ((ReentrantReadWriteLock) readWriteLock).getReadHoldCount() == 0;
                assert ((ReentrantReadWriteLock) readWriteLock).getWriteHoldCount() == 0;
            }
        }
    } finally {
        if (!downgraded) {
            // Never (we were holding a W lock while trying a R lock).
            readWriteLock.writeLock().unlock();
        }
        assert ((ReentrantReadWriteLock) readWriteLock).getReadHoldCount() == 0;
        assert ((ReentrantReadWriteLock) readWriteLock).getWriteHoldCount() == 0;
    }
}

Qual foi a idéia por trás de não permitir uma atualização de bloqueio de maneira semelhante? O método tryLock() para um bloqueio Write abaixo poderia retornar com segurança true w / oa risco de um deadlock na ausência de outros threads contendo um bloqueio Read :

void upgrade(final ReadWriteLock readWriteLock) {
    readWriteLock.readLock().lock();
    try {
        // Always false: lock upgrade is not allowed
        final boolean writeLockAcquired = readWriteLock.writeLock().tryLock();
        // ...
    } finally {
        readWriteLock.readLock().unlock();
    }
}
    
por Bass 26.10.2015 в 10:43
fonte

1 resposta

2

Primeiramente, vamos observar que a atualização e o downgrade não são equivalentes em termos de complexidade semântica para ReadWriteLock s.

Você não precisa afastar a contenção para concluir uma transação de downgrade, porque você já está mantendo os privilégios mais escalados no bloqueio e porque é garantido que você seja o único thread que atualmente está realizando um downgrade. O mesmo não acontece com a atualização, portanto, o mecanismo que faz o upgrade naturalmente precisa ser mais complexo (ou muito mais inteligente).

Para ser utilizável, o mecanismo de upgrade precisa evitar deadlocks no caso de dois threads de leitura tentarem atualizar simultaneamente (ou especificamente para ReentrantReadWriteLock , caso um único encadeamento de leitura mantendo vários bloqueios de leitura tente atualizar). Além disso, o mecanismo precisa especificar como a solicitação de upgrade com falha seria tratada (seu bloqueio de leitura será invalidado) e isso é ainda menos trivial.

Como você provavelmente já deve ter percebido, lidar totalmente com esses problemas em ReentrantReadWriteLock é inconveniente, para dizer o mínimo (ainda assim, isso é o que é .NET. ReaderWriterLock tenta e acho que realmente consegue fazer). Meu palpite é que enquanto final boolean writeLockAcquired = readWriteLock.writeLock().tryLock(); poderia ter sido feito para ter sucesso em alguns casos triviais, a capacidade de atualização ainda não teria sido boa o suficiente para uso comum - sob forte disputa, se você perder a corrida pelo bloqueio de gravação, no mesmo barco, como se você tivesse desbloqueado o bloqueio de leitura e tentasse obter o bloqueio de gravação (deixando a oportunidade de alguém entrar e tirar o bloqueio de gravação entre eles).

Uma boa maneira de fornecer a capacidade de atualização de bloqueio é permitir que apenas um único thread tente atualizar - isso é o que ReentrantReadWriteUpdateLock faz ou o que é .NET ReaderWriterLockSlim . No entanto, eu ainda recomendaria o StampedLock do Java 8 como:

  • sob baixa contenção, suas leituras otimistas são muito mais rápidas do que usar bloqueios de leitura
  • sua API é muito menos restritiva sobre a atualização (da leitura otimista para a leitura de bloqueio para bloqueio de gravação)
  • minhas várias tentativas de criar um benchmark JMH realista em que um dos outros bloqueios similares bate quase sempre falhou
por Dimitar Dimitrov 12.04.2016 / 01:09
fonte