Java Notes

Home首页 / Lesson 7: Threads第 7 课:线程

Lesson 7 · Concurrency第 7 课 · 并发

Why is HashMap not thread-safe?为什么 HashMap 是线程不安全的?

Two threads write at the same time, one write is lost. In JDK 7 it could even bend into a circle.两个线程同时写,就会丢掉一次写入;在 JDK 7 里甚至能把链表拧成一个圈。

Short answer先给答案HashMap is not thread-safe because put is a multi-step write: two threads can overwrite each other's size, lose a node, and in JDK 7 even bend a list into a circle so get loops forever. In a web app use ConcurrentHashMap, which locks only one bucket.HashMap 线程不安全,因为 put 是多步写入:两个线程会互相覆盖 size、丢掉节点;在 JDK 7 里甚至能把链拧成环,让 get 永远转不出来。Web 应用里用 ConcurrentHashMap,它只锁一个桶。

Basics基础concurrency并发must know必背

01The idea先有一个画面

Think of it like this先这样想Two people write on the same whiteboard at the same time. Both read “size = 10”, both write “size = 11”. One item was added, but the number only went up by one — the other add is invisible.两个人在同一块白板上同时写。两个人都读到 size = 10,都写下 size = 11。 明明加了两个,数字只涨了 1——另一次写入消失了。

put is not one instruction. It is a series of steps: pick a bucket, read the head, write the new node, increase size. Another thread can jump in between any two steps.put 不是一条指令,而是一串步骤:选桶、读头节点、写新节点、size++。另一个线程可以在任意两步之间插进来。

02Three ways it breaks三种坏法

Java 8 changed resize to keep the order (no circle), but data loss and wrong size still happen. “No circle” is not “thread-safe”.Java 8 把扩容改成保序(不会再成环),但丢数据和 size 不准依然存在。“不成环”不等于“线程安全”。

03How to fix it怎么解决

Option方案Good for适合Watch out注意
ConcurrentHashMapweb apps, shared cachesWeb 应用、共享缓存null keys not allowed不允许 null key
Collections.synchronizedMaprare, small maps很少用、很小的表one lock for everything整表一把锁,慢
ThreadLocalper-user / per-request data每用户、每请求的数据memory leak in thread pools线程池里会内存泄漏
Local variable局部变量inside one method方法内部nothing没有

04One sentence for the interview面试用一句话

Say it like this可以这样说

HashMap is not thread-safe because put is a multi-step write: two threads can overwrite each other's size, lose a node, or in JDK 7 even build a circle and loop forever. In a web application I use ConcurrentHashMap.HashMap 线程不安全,因为 put 是多步写入:两个线程会互相覆盖 size、丢节点,JDK 7 里甚至会成环死循环。Web 应用里我用 ConcurrentHashMap。

→New words新词

thread /θred/ One worker inside a program.线程:程序里的一个执行单位 ▶ hear it
concurrent /kənˈkɜːrənt/ Happening at the same time.并发的:同时发生 ▶ hear it
race condition /reɪs kənˈdɪʃn/ The result depends on who runs first.竞态:结果取决于谁先跑 ▶ hear it
lock /lɑːk/ Let only one worker in.锁:只让一个线程进 ▶ hear it
synchronized /ˈsɪŋkrənaɪzd/ Java keyword: one thread at a time.synchronized:Java 关键字,一次只一个线程 ▶ hear it
volatile /ˈvɑːlətl/ Java keyword: always read the newest value.volatile:总是读最新值 ▶ hear it

Click ▶ hear it for real people on YouGlish; 🔊 for the computer voice.点 ▶ hear it 听真人发音(YouGlish);点 🔊 听电脑朗读。

✓Check yourself自测

  1. Why can size be wrong after two puts?为什么两次 put 之后 size 会不准?
  2. What was special about JDK 7 resize?JDK 7 的扩容有什么特别之处?
  3. What would you use in a Spring service, and why?Spring 服务里你会用什么?为什么?

Answer out loud first. Only then open the answers below.先自己出声答,再看下面的答案。

Answers参考答案
01Both threads read 10 and write 11. Two nodes were added, but the number went up by one.两个线程都读到 10、都写 11。实际加了两个节点,数字只涨了 1。
02It used head insertion, so the list was reversed during resize. Two threads could make a node point back — a circle.扩容用头插法,链表会被倒置;两个线程同时做,就可能让某个节点指回去,形成环。
03ConcurrentHashMap: one bucket is locked at a time, so reads stay fast and writes stay safe.ConcurrentHashMap:一次只锁一个桶,读照样快,写也安全。

Next: the whole HashMap answer →下一步:HashMap 整道题 →