You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程下SdkWrapper初始化并发安全问题及解决问询

How to Safely Initialize an SDK Once and Ensure Proper Execution Order in Kotlin Coroutines

Let's break down your problem clearly: you need to guarantee three core requirements:

  1. Sdk.init() is called exactly once
  2. useSdk() only runs after Sdk.init() completes successfully
  3. Concurrent calls to doSomething() don't break either rule, even when triggered rapidly in succession

Your initial attempts ran into issues because synchronized isn't compatible with coroutine suspend points, and naive flag checks don't account for in-progress initialization. Here are two robust, coroutine-friendly solutions:


Solution 1: Use Mutex (Simple & Straightforward)

Kotlin Coroutines provides Mutex—a suspending lock designed to work with suspend functions without blocking threads. This is the simplest fix for your scenario:

class SdkWrapper(private val sdk: Sdk) {
    private var inited = false
    // Create a mutex to guard the initialization logic
    private val initMutex = Mutex()

    suspend fun doSomething() = withContext(Dispatchers.IO) {
        // Only one coroutine can execute this block at a time
        initMutex.withLock {
            if (!inited) {
                sdk.init()
                inited = true
            }
        }
        // By this point, initialization is guaranteed to be complete
        useSdk()
    }

    private fun useSdk() { }
}

class Sdk {
    fun init() {}
}

How this works:

  • initMutex.withLock ensures exclusive access to the initialization logic. The first coroutine to call doSomething() acquires the lock, runs sdk.init(), and marks inited as true.
  • Subsequent coroutines suspend (instead of blocking threads) until the lock is released, then skip initialization entirely once they see inited = true.
  • The outer withContext(Dispatchers.IO) ensures all operations run on an IO thread, and Mutex plays nicely with coroutine context switches.

Solution 2: Use CompletableDeferred (For Advanced Error Handling)

If you need to handle cases where sdk.init() might fail (and want to retry or propagate errors), using a CompletableDeferred with an atomic reference is a more flexible option. This tracks initialization state atomically:

import kotlinx.coroutines.CompletableDeferred
import java.util.concurrent.atomic.AtomicReference

class SdkWrapper(private val sdk: Sdk) {
    // Track the initialization job atomically to prevent duplicates
    private val initJob = AtomicReference<CompletableDeferred<Unit>?>(null)

    suspend fun doSomething() = withContext(Dispatchers.IO) {
        val deferred = initJob.get() ?: run {
            val newDeferred = CompletableDeferred<Unit>()
            // Atomically set the deferred only if it's null (prevents duplicate init)
            if (initJob.compareAndSet(null, newDeferred)) {
                try {
                    sdk.init()
                    newDeferred.complete(Unit)
                } catch (e: Exception) {
                    newDeferred.completeExceptionally(e)
                    // Optional: Reset the job to allow retries if initialization fails
                    initJob.set(null)
                }
            } else {
                // Another coroutine already started initialization
                initJob.get()!!
            }
        }

        // Wait for initialization to finish (success or failure)
        deferred.await()
        useSdk()
    }

    private fun useSdk() { }
}

class Sdk {
    fun init() {}
}

How this works:

  • AtomicReference ensures only the first coroutine can create a CompletableDeferred and start initialization.
  • All subsequent coroutines wait for the existing deferred to complete before running useSdk().
  • If initialization fails, you can choose to reset the initJob to allow future calls to retry, or leave it to propagate the exception to all waiting coroutines.

Why Your Previous Attempts Failed

Let's quickly recap why your initial approaches didn't work:

  • Naive flag check: When two coroutines call doSomething() rapidly, both see inited = false before the first finishes sdk.init(), leading to duplicate initialization.
  • Setting inited before sdk.init(): The second coroutine sees inited = true immediately and runs useSdk() before sdk.init() completes, violating the precondition.
  • Using synchronized: synchronized is a thread-level lock, but withContext switches threads. The lock gets released when the coroutine suspends, so subsequent coroutines can enter the block before initialization finishes. IntelliJ's error correctly flags this as unsafe.

内容的提问来源于stack exchange,提问作者fweigl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 14:57:31