Now that the new manager allows shared mutable state across threads, what tools do you use to keep that state correct, and what does the GC NOT do for you?
answer
- GC = memory safety; you = thread safety
- AtomicInt/AtomicReference, @Volatile for lock-free
- Mutex.withLock for suspend mutual exclusion
- Confine via single-thread context / @ThreadLocal
- GC gives no atomicity/visibility/ordering
basics
~10 sThe garbage collector only manages memory; it does not stop two threads from changing the same data at once. You protect shared data with atomics, locks/mutexes, or by keeping it inside one thread.
solid answer
~40 sThe new MM removes thread-ownership but not the rules of concurrency. To keep shared mutable state correct you use: `kotlin.concurrent.AtomicInt`, `AtomicLong`, `AtomicReference`, and `@Volatile` for simple lock-free fields; `kotlinx.coroutines.sync.Mutex` (`withLock { }`) for suspendable mutual exclusion; or single-thread confinement (an actor-like coroutine, `newSingleThreadContext`, or `@ThreadLocal` per-thread state). The kotlinx.atomicfu library provides multiplatform atomics. The GC only reclaims unreachable memory and prevents use-after-free; it does **not** provide atomicity, visibility, or ordering guarantees, and it will not detect or prevent data races. So `counter++` from two threads is still broken even though it compiles and never crashes the runtime — you'd lose updates. Treat Native concurrency exactly like the JVM: shared mutability demands explicit synchronization.
code
kotlin · 8 lines// BROKEN even on the new MM: data race, lost updates
var plain = 0
fun racyBump() { plain++ } // no crash, but wrong under concurrency
// CORRECT: atomic
import kotlin.concurrent.AtomicInt
val safe = AtomicInt(0)
fun safeBump() { safe.incrementAndGet() }go deeper
Understands the GC only frees memory and you still need locks/atomics for shared data.
Names AtomicInt/AtomicReference, @Volatile, and Mutex, and knows the GC gives no atomicity/visibility.
Chooses between atomics, Mutex, and confinement appropriately and flags the migration race risk.
Designs concurrency invariants for shared KMP state and reviews legacy-MM code for newly-exposed races.
## The core distinction - **Memory safety** (GC's job): no dangling pointers, no use-after-free, unreachable objects reclaimed. - **Thread safety** (your job): correct results when multiple threads touch shared mutable state. The new MM gives you memory safety **and** the freedom to share mutable objects — but the second freedom hands you the JVM's classic problem: **data races**. ## Tools for correctness 1. **Atomics** — `kotlin.concurrent.AtomicInt / AtomicLong / AtomicReference` (stdlib, multiplatform) for lock-free counters/flags. `compareAndSet`, `getAndIncrement`, etc. 2. **`@Volatile`** — ensures visibility/ordering of a single field across threads (no compound-action atomicity). 3. **`Mutex` from kotlinx.coroutines** — `mutex.withLock { ... }` for suspend-friendly mutual exclusion (does not block the thread). 4. **Confinement** — keep mutable state inside one coroutine/thread: `newSingleThreadContext`, an actor pattern, or `@ThreadLocal`. 5. **kotlinx.atomicfu** — library giving efficient multiplatform atomic fields with a clean API. ```kotlin import kotlin.concurrent.AtomicInt import kotlinx.coroutines.* val counter = AtomicInt(0) fun main() = runBlocking { coroutineScope { repeat(1000) { launch(Dispatchers.Default) { counter.incrementAndGet() } } } println(counter.load()) // 1000 — correct because atomic, not because of the GC } ``` ## What the GC explicitly does NOT do - It does **not** make `counter++` atomic. - It does **not** guarantee one thread sees another's writes (no implicit memory-visibility for plain fields). - It does **not** order operations or insert barriers for you. - It does **not** detect or report data races. ## Practical guidance Audit every piece of state shared across coroutines/threads. Either make it immutable, confine it, or guard it with atomics/`Mutex`. The migration from the legacy MM is dangerous precisely here: code that used to be 'safe' because objects were frozen now silently allows racy mutation.
- If code never crashes after migration, does that mean it's thread-safe?No. The GC prevents memory crashes, not logical races. Unsynchronized shared mutation can silently lose updates or corrupt invariants without any exception.
- Which API gives suspend-friendly mutual exclusion without blocking the thread?kotlinx.coroutines.sync.Mutex via mutex.withLock { }; unlike a blocking lock it suspends the coroutine instead of parking the thread.
The GC is a janitor who clears empty desks. It won't stop two people from writing on the same whiteboard simultaneously — you still need to hand around a single marker (a lock).
saying these in an interview costs you the question
- Believing the GC makes increments atomic
- Assuming no exception means no concurrency bug
- Thinking @Volatile makes compound operations atomic
- Using a blocking lock inside coroutines instead of Mutex
- Saying synchronization is unnecessary on Native now