Как синхронизировать коллекции

В Kotlin для синхронизации коллекций между потоками можно использовать несколько подходов.

Использование synchronized и стандартных коллекций

import kotlin.concurrent.thread

val list = mutableListOf<String>()
val lock = Any()

fun addItem(item: String) {
    synchronized(lock) {
        list.add(item)
    }
}

fun getItem(index: Int): String? {
    return synchronized(lock) {
        list.getOrNull(index)
    }
}

fun main() {
    // Поток 1
    thread {
        repeat(10) {
            addItem("Item $it from Thread 1")
            println("Thread 1 added item $it")
        }
    }

    // Поток 2
    thread {
        repeat(10) {
            addItem("Item $it from Thread 2")
            println("Thread 2 added item $it")
        }
    }

    // Дадим потокам время на выполнение
    Thread.sleep(1000)

    // Вывод содержимого коллекции
    println("Final list: $list")
}

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

Использование потокобезопасных коллекций из java.util.concurrent

Если вам нужна коллекция, которая уже поддерживает синхронизацию, вы можете использовать коллекции из java.util.concurrent. Например, CopyOnWriteArrayList:

import java.util.concurrent.CopyOnWriteArrayList
import kotlin.concurrent.thread

val list = CopyOnWriteArrayList<String>()

fun addItem(item: String) {
    list.add(item)
}

fun getItem(index: Int): String? {
    return list.getOrNull(index)
}

fun main() {
    // Поток 1
    thread {
        repeat(10) {
            addItem("Item $it from Thread 1")
            println("Thread 1 added item $it")
        }
    }

    // Поток 2
    thread {
        repeat(10) {
            addItem("Item $it from Thread 2")
            println("Thread 2 added item $it")
        }
    }

    // Дадим потокам время на выполнение
    Thread.sleep(1000)

    // Вывод содержимого коллекции
    println("Final list: $list")
}

CopyOnWriteArrayList автоматически синхронизирует доступ и создаёт копии массива при изменениях, что делает её подходящей для сценариев, когда изменения происходят редко, а чтения часты.

Использование Mutex из корутин

import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

val list = mutableListOf<String>()
val mutex = Mutex()

suspend fun addItem(item: String) {
    mutex.withLock {
        list.add(item)
    }
}

suspend fun getItem(index: Int): String? {
    return mutex.withLock {
        list.getOrNull(index)
    }
}

fun main() = runBlocking {
    // Поток 1
    val job1 = launch {
        repeat(10) {
            addItem("Item $it from Coroutine 1")
            println("Coroutine 1 added item $it")
        }
    }

    // Поток 2
    val job2 = launch {
        repeat(10) {
            addItem("Item $it from Coroutine 2")
            println("Coroutine 2 added item $it")
        }
    }

    // Ожидание завершения обоих потоков
    joinAll(job1, job2)

    // Вывод содержимого коллекции
    println("Final list: $list")
}

В этом случае Mutex обеспечивает безопасный доступ к коллекции из разных корутин. Использование withLock гарантирует, что только один поток (или корутина) может выполнять критическую секцию кода в один момент времени.