MVP
MVP

Reputation: 181

Kotlin - How to lock a collection when accessing it from two threads

wondered if anyone could assist, I'm trying to understand the correct way to access a collection in Kotlin with two threads.

The code below simulates a problem I'm having in a live system. One thread iterates over the collection but another thread can remove elements in that array.

I have tried adding @synchronized to the collections getter but that still gives me a concurrentmodification exception.

Can anyone let me know what the correct way of doing this would be?

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) }
    }
}

Upvotes: 7

Views: 11578

Answers (2)

Tenfour04
Tenfour04

Reputation: 93872

You are only synchronizing the getter and setter, so when you start using the reference you get to the list, it is already unlocked.

Kotlin has the Mutex class available for locking manipulation of a shared mutable object. Mutex is nicer than Java's synchronized because it suspends instead of blocking the coroutine thread.

Your example would be poor design in the real world because your class publicly exposes a mutable list. But going along with making it at least safe to modify the list:

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) } }
    }
}

If you are not working with coroutines, instead of a Mutex(), you can use an Any and instead of withLock use synchronized (myListLock) {} just like you would in Java to prevent code from within the synchronized blocks from running at the same time.

Upvotes: 11

mightyWOZ
mightyWOZ

Reputation: 8355

If you want to lock a collection, or any object for concurrent access, you can use the almost same construct as java's synchronized keyword.

So while accessing such an object you would do

fun someFun() {
    synchronized(yourCollection) {

    }
}

You can also use synchronizedCollection method from java's Collections class, but this only makes single method access thread safe, if you have to iterate over the collection, you will still have to manually handle the synchronization.

Upvotes: 2

Related Questions