Irbisa · Шпаргалка
Многопоточность и коллекции JVM
Потоки и Java Memory Model, volatile и synchronized, блокировки, executors, потокобезопасные коллекции, устройство HashMap
Чем процесс отличается от потока, и в каких состояниях может находиться поток JVM?
Где в JVM живут локальные переменные, примитивы и объекты, в стеке или в куче, и что из этого могут делить два потока?
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() }…Как до корутин Android-приложения уводили работу с главного потока (
Thread,HandlerThread,Executor,AsyncTask), и почемуAsyncTaskобъявили устаревшим?// 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) {…ArrayListилиLinkedList: сколько стоятget,addиremoveв каждом, и почемуArrayListпочти всегда быстрее?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)…HashMap,LinkedHashMapилиTreeMap(и их множества): какой порядок и какую стоимость поиска даёт каждый, и что возвращаетmutableMapOf()?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…Как
HashMapустроен внутри: бакеты,hashCodeиequals, коллизии, load factor, ресайз и превращение бакета в дерево?Что такое happens-before в модели памяти Java, и что гарантируют
synchronized,@VolatileиAtomicInteger?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…Найдите баг:
stop()выставляетrunning = false, но иногда рабочий поток так и не выходит из цикла.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 }}Как правильно остановить поток, что на самом деле делает
interrupt(), и почему нельзя глотатьInterruptedException?class Uploader(private val queue: BlockingQueue<File>) { private val worker = Thread { try { while (!Thread.currentThread().isInterrupted) { upload(queue.take()) // take() throws InterruptedException }…synchronized,ReentrantLockилиReentrantReadWriteLock: что добавляет явная блокировка, и что значит «реентерабельная»?class TokenStore { private val lock = ReentrantLock() private var token: String? = null fun tryRead(): String? = if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {…Два потока одновременно вызывают
transfer(a, b)иtransfer(b, a), и оба зависают навсегда. Почему, и как от этого защищают порядок блокировок иtryLock?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)Как передавать работу от потоков-производителей потоку-потребителю:
wait/notifyилиBlockingQueue, и почемуwait()должен стоять в цикле?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}…Как работает пул потоков, чем отличаются
newFixedThreadPool,newCachedThreadPoolиnewSingleThreadExecutor, и что происходит, когда задачи копятся?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)…ConcurrentHashMap,Collections.synchronizedMapиCopyOnWriteArrayList: как каждый обеспечивает потокобезопасность, и чего это стоит?val counts = ConcurrentHashMap<String, Int>()fun record(event: String) { counts.merge(event, 1, Int::plus) // atomic: no lost increments} val sessions = ConcurrentHashMap<String, Session>()…Найдите баг: два экрана одновременно запрашивают одного пользователя, и кеш дважды грузит его из сети, хотя мапа у него
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}Как атомики на CAS вроде
AtomicReferenceработают без блокировки, и где они перестают помогать: конкуренция, ABA, обновление нескольких полей?data class Stats(val count: Int, val total: Long) class Metrics { private val stats = AtomicReference(Stats(0, 0)) fun record(ms: Long) {…Что может пойти не так, когда два потока одновременно пишут в обычный
HashMap, и почему недостаточно обернуть вsynchronizedтолькоput?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 }…Что такое
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 {…