Лекция 7.2. Сложные блокировки

Мы раньше строили таблицу конфликтов для регистров, но мы можем так же делать и для своих структур. Например, для стека:

sizepushpop
sizexx
pushxxx
popxxx

Если у нас грубая блокировка, то мы исключили size-size, но это не нужно. Чтобы не блокать то, что не нужно, придуманы read-write lock'и, которые имеют два режима: Read (Shared) и Write (Exclusive).

RW
Rx
Wxx

Пример:

private val lock = ReentrantReadWriteLock()

val size: Int get() = lock.readLock().withLock {
    top
}

fun push(item: T) = lock.writeLock().withLock {
    data[top++] = item
}

fun pop(): T = lock.writeLock().withLock {
    data[--top]
}

Хотелось бы сделать что-то такое, но это deadlock потока с самим собой:

val lock = ReentrantReadWriteLock()
lock.readLock().withLock {
    println("reading")
    lock.writeLock().withLock {
        println("writing")
    }
}

Казалось бы, можно придумать UpgradableReadWriteLock со следующей матрицей совместимости:

\(R_P\)\(R_Q\)\(W_P\)\(W_Q\)
\(R_P\)x
\(R_Q\)x
\(W_P\)xx
\(W_Q\)xx

Однако если несколько потоков хотят upgradeнуться, то есть опасность deadlockа. Тогда создадим состояние "Intent to write", которое будет эксклюзивным:

RIWW
Rx
IWxx
Wxxx

Напишем такой хитрый лок.

private const val R_BIT = 1
private const val R_MASK = (1 shl 30) - 1
private const val IW_BIT = 1 shl 30
private const val W_BIT = 1 shl 31

class Sync : AbstractQueuedSynchronizer() {
    override fun tryAcquireShared(arg: Int): Int { // R или IW
        while (true) {
            val state = this.state
            if (state and W_BIT != 0) // если кто-то W, то нельзя
                return -1
            // если кто-то еще IW и мы хотим IW, то нельзя
            if (arg == IW_BIT && state and IW_BIT != 0) 
                return -1
            val update = state + arg
            if (compareAndSetState(state, update))
                return 1
        }
    }
    
    override fun tryReleaseShared(arg: Int): Boolean {
        while (true) {
            val state = this.state
            val update = state - arg
            if (compareAndSetState(state, update))
                return update and (R_MASK or IW_BIT) == 0
        }
    }
    
    override fun tryAcquire(arg: Int): Boolean { // тривиально
        while (true) {
            val state = this.state
            if (state != 0)
                return false
            if (compareAndSetState(state, state or W_BIT))
                return true
        }
    }
    
    override fun tryRelease(arg: Int): Boolean {
        while (true) {
            val state = this.state
            val update = state and W_BIT.inv()
            if (compareAndSetState(state, update))
                return true
        }
    }
}

Ещё нужно в ThreadLocal состоянии держать сколько локов каждого типа мы держим, чтобы если мы держим лок, не брать его физически.