Kotlin - Как заблокировать коллекцию при доступе к ней из двух потоков - PullRequest
0 голосов
/ 09 февраля 2020

Интересно, если кто-нибудь может помочь, я пытаюсь понять, как правильно получить доступ к коллекции в Kotlin с двумя потоками.

Код ниже имитирует проблему, с которой я сталкиваюсь в живой системе , Один поток выполняет итерацию по коллекции, но другой поток может удалять элементы из этого массива.

Я пытался добавить @synchronized в сборщик коллекций, но это все равно дает мне исключение одновременной модификации.

Может кто-нибудь разрешить я знаю, как правильно это сделать?

class ListTest() {

    val myList = mutableListOf<String>()
        @Synchronized
        get() = field

    init {
        repeat(10000) {
            myList.add("stuff: $it")
        }
    }
}

fun main() = runBlocking<Unit> {

    val listTest = ListTest()

    launch(Dispatchers.Default) {
        delay(1L)
        listTest.myList.remove("stuff: 54")
    }

    launch {
        listTest.myList.forEach { println(it) }
    }
}

Ответы [ 2 ]

1 голос
/ 09 февраля 2020

Вы синхронизируете только геттер и сеттер, поэтому, когда вы начинаете использовать ссылку, попадающую в список, она уже разблокирована.

Kotlin имеет класс Mutex, доступный для блокировки манипуляций совместно используемого ресурса. изменчивый объект Mutex лучше, чем Java synchronized, потому что он приостанавливается вместо блокировки потока сопрограммы.

Ваш пример будет плохим дизайном в реальном мире, потому что ваш класс публично предоставляет изменяемый список. Но, сделав это, сделайте хотя бы безопасным изменение списка:

class ListTest() {

    private val myListMutex = Mutex()

    private val myList = mutableListOf<String>()

    init {
        repeat(10000) {
            myList.add("stuff: $it")
        }
    }

    suspend fun modifyMyList(block: MutableList<String>.() -> Unit) {
        myListMutex.withLock { myList.block() }
    }
}

fun main() = runBlocking<Unit> {

    val listTest = ListTest()

    launch(Dispatchers.Default) {
        delay(1L)
        listTest.modifyMyList { it.remove("stuff: 54") }
    }

    launch {
        listTest.modifyMyList { it.forEach { println(it) } }
    }
}

Если вы не работаете с сопрограммами, вместо Mutex() вы можете использовать Any и вместо withLock используйте synchronized (myListLock) {} так же, как в Java, чтобы предотвратить одновременный запуск кода из синхронизированных блоков.

0 голосов
/ 09 февраля 2020

Если вы хотите заблокировать коллекцию или любой объект для одновременного доступа, вы можете использовать почти такую ​​же конструкцию, как ключевое слово java *. 1001 *.

Так что при доступе к такому объекту вы бы do

fun someFun() {
    synchronized(yourCollection) {

    }
}

Вы также можете использовать метод synchronizedCollection из класса java Collections, но это делает потокобезопасным доступ только для одного метода, если вам нужно iterate над collection вам все равно придется вручную обрабатывать синхронизацию.

...