Irbisa · Cheatsheet

Java Concurrency & Collections

Threads and the Java memory model, volatile and synchronized, locks, executors, concurrent collections, HashMap internals

Middle Developer · 18 questions

  1. 01

    What is the difference between a process and a thread, and which states can a JVM thread be in?

    A process is a running program with its own memory; threads run inside a process, share its heap and each have their own stack.

  2. 02

    Where do local variables, primitives and objects live in the JVM, stack or heap, and which of them can two threads share?

    Each thread has its own stack of call frames holding locals, parameters and references; every object lives on the one heap all threads share.

    fun countInParallel(): Int {    var count = 0                        // captured var: becomes a Ref.IntRef on the heap    val workers = List(4) {        Thread { repeat(100_000) { count++ } }    }    workers.forEach { it.start() }…
  3. 03

    Before coroutines, how did Android apps move work off the main thread (Thread, HandlerThread, Executor, AsyncTask), and why was AsyncTask deprecated?

    With a raw Thread, a HandlerThread that has its own Looper, an ExecutorService plus a main-thread Handler, or AsyncTask, which bundled that pair.

    // Java-era pattern: an executor for the work, a main-thread Handler for the resultclass UserLoader(private val api: UserApi) {    private val executor = Executors.newSingleThreadExecutor()    private val main = Handler(Looper.getMainLooper())     fun load(id: Long, onResult: (User) -> Unit) {…
  4. 04

    ArrayList vs LinkedList: what do get, add and remove cost in each, and why is ArrayList almost always the faster choice?

    ArrayList is a resizable array: get(i) is O(1), appending is amortised O(1), inserting or removing in the middle is O(n).

    fun demo() {    val linked = LinkedList((1..20_000).toList())    var sum = 0L    for (i in linked.indices) sum += linked[i]   // wrong: each get(i) walks the chain, O(n²)    for (x in linked) sum += x                   // right: one walk, O(n)…
  5. 05

    HashMap, LinkedHashMap or TreeMap (and their sets): what order and lookup cost does each give, and which one does mutableMapOf() return?

    HashMap gives O(1) average lookups in no defined order, LinkedHashMap also keeps insertion order, and TreeMap keeps keys sorted at O(log n) per operation.

    fun demo() {    val scores = mapOf("mia" to 1, "zoe" to 3, "adam" to 5)        // LinkedHashMap    println(scores.keys)                                          // [mia, zoe, adam]: insertion order    println(scores.toSortedMap().keys)                            // [adam, mia, zoe]: TreeMap    println(hashMapOf("mia" to 1, "zoe" to 3, "adam" to 5).keys)  // [zoe, adam, mia]: bucket order…
  6. 06

    How does HashMap work internally: buckets, hashCode and equals, collisions, load factor, resizing and treeification?

    HashMap keeps an array of buckets: the key's hashCode picks a bucket and equals finds the entry inside it.

  7. 07

    What is happens-before in the Java Memory Model, and what do synchronized, @Volatile and AtomicInteger each guarantee?

    Happens-before is the Java Memory Model's rule for when one thread must see another's writes; without that edge the JIT and CPU may cache or reorder them.

    class Stats {    private val lock = Any()    private var total = 0L                       // guarded by lock     @Volatile var lastUser: String? = null       // one writer: visibility is enough    private val hits = AtomicInteger()           // atomic read-modify-write…
  8. 08

    Find the bug: stop() sets running = false, but sometimes the worker thread never leaves its loop.

    class Worker {    private var running = true    private var processed = 0L     fun start() {        Thread {            while (running) {                processed++        // stands in for work with no calls or locks            }            println("stopped after $processed steps")        }.start()    }     fun stop() {        running = false            // called from the main thread    }}

    running is a plain field, so nothing guarantees the worker ever sees the main thread's write: the compiler may read it once before the loop and spin on that copy forever.

  9. 09

    How do you stop a thread correctly, what does interrupt() actually do, and why must you never swallow InterruptedException?

    You ask it to stop and it stops itself: interrupt() only sets a flag on the target thread.

    class Uploader(private val queue: BlockingQueue<File>) {    private val worker = Thread {        try {            while (!Thread.currentThread().isInterrupted) {                upload(queue.take())             // take() throws InterruptedException            }…
  10. 10

    synchronized vs ReentrantLock vs ReentrantReadWriteLock: what does an explicit lock add, and what does reentrant mean?

    All three are reentrant: a thread that holds the lock can take it again without blocking itself.

    class TokenStore {    private val lock = ReentrantLock()    private var token: String? = null     fun tryRead(): String? =        if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {…
  11. 11

    Two threads call transfer(a, b) and transfer(b, a) at the same moment and both freeze forever. Why, and how do lock ordering and tryLock prevent it?

    class Account(val id: Long, var balance: Long) fun transfer(from: Account, to: Account, amount: Long) {    synchronized(from) {        synchronized(to) {            from.balance -= amount            to.balance += amount        }    }} // thread 1: transfer(a, b, 10)// thread 2: transfer(b, a, 5)

    Thread 1 holds a and waits for b, thread 2 holds b and waits for a: a cycle neither can leave, which is a deadlock.

  12. 12

    How do you hand work from producer threads to a consumer thread: wait/notify or a BlockingQueue, and why must wait() sit in a loop?

    Use a BlockingQueue: put blocks while the queue is full, take blocks while it is empty, and the library does the locking.

    val jobs = ArrayBlockingQueue<Job>(100) fun produce(job: Job) = jobs.put(job)       // blocks while 100 jobs are waitingfun consumeLoop() {    while (true) process(jobs.take())       // blocks while the queue is empty}…
  13. 13

    How does a thread pool work, how do newFixedThreadPool, newCachedThreadPool and newSingleThreadExecutor differ, and what happens when tasks pile up?

    A pool keeps threads alive and feeds them tasks from a queue, so you pay for creating a thread once.

    val pool = ThreadPoolExecutor(    2, 4,                                   // core and maximum threads    30, TimeUnit.SECONDS,                   // idle extra threads stop after 30 s    ArrayBlockingQueue(100),                // bounded: at most 100 waiting tasks    ThreadPoolExecutor.CallerRunsPolicy(),  // full: the caller runs the task itself)…
  14. 14

    ConcurrentHashMap, Collections.synchronizedMap and CopyOnWriteArrayList: how does each make access thread-safe, and what does each cost?

    synchronizedMap wraps every call in one lock, so all threads queue for it.

    val counts = ConcurrentHashMap<String, Int>()fun record(event: String) {    counts.merge(event, 1, Int::plus)           // atomic: no lost increments} val sessions = ConcurrentHashMap<String, Session>()…
  15. 15

    Find the bug: two screens ask for the same user at once and the cache loads it from the network twice, although the map is a ConcurrentHashMap.

    class UserCache(private val api: UserApi) {    private val users = ConcurrentHashMap<Long, User>()     // Called from several threads at once.    fun user(id: Long): User =        users.getOrPut(id) { api.loadUser(id) }   // blocking network call}

    getOrPut is check-then-act: it reads the map and, on a miss, runs the loader and stores the result with putIfAbsent.

  16. 16

    How do CAS-based atomics like AtomicReference work without a lock, and where do they stop helping: contention, ABA, multi-field updates?

    Compare-and-set is one atomic hardware operation: write the new value only if the field still holds the expected one, and report whether it did.

    data class Stats(val count: Int, val total: Long) class Metrics {    private val stats = AtomicReference(Stats(0, 0))     fun record(ms: Long) {…
  17. 17

    What can go wrong when two threads write to a plain HashMap at the same time, and why is wrapping only put in synchronized not enough?

    Entries silently disappear: two puts into one bucket overwrite each other's link, a write during a resize lands in the old array, and size drifts from the real count.

    class SessionRegistry {    private val lock = Any()    private val sessions = HashMap<String, Session>()     // wrong: writers take the lock, the reader does not    fun addRacy(id: String, s: Session) = synchronized(lock) { sessions[id] = s }…
  18. 18

    What is ThreadLocal, how does it leak or hand stale values to the next task on a thread pool, and what changes inside coroutines?

    ThreadLocal gives each thread its own copy of a value, stored in a map inside the Thread object and keyed by the ThreadLocal.

    private val requestId = ThreadLocal<String?>() // On a pool: always clean up, or the next task on this thread inherits the valuefun handle(pool: ExecutorService, id: String, work: () -> Unit) = pool.execute {    requestId.set(id)    try {…