7. 线程安全类

概念

普通 HashMap、ArrayList 和 Python 的 dict、list 允许多个线程同时读,但不允许在没有协调的情况下同时改。两个线程同时执行 count = count + 1,结果可能少加一次。 Java 并发包把常见需求做成了线程安全类:AtomicInteger 做计数,ConcurrentHashMap 做映射,BlockingQueue 做移交。Python 标准库里 Queue 是线程安全的;自己保护一个 dict 时,用同一把 Lock 包住读改写。

  • 原子类适合单个数值。compareAndSet 可以在不加重锁的情况下完成判断并更新。
  • ConcurrentHashMap 的单个方法是线程安全的,但“先查再放”这种组合动作仍要用 compute 一次做完。
  • 阻塞队列同时解决移交和等待:队列空时 take 休眠,队列满时 put 休眠。
  • 线程安全类缩小了加锁范围,不是所有共享数据都可以不加思考地并发写。

算法流程

下面的 UML 图说明 Java 例程和 Python 例程共同实现的流程。实线箭头表示调用或发送,虚线箭头表示返回或唤起。

并发计数(UML 时序图)

sequenceDiagram participant T1 as 线程 1 participant Counter as 原子计数或同一把锁 participant Map as 并发映射 participant T2 as 线程 2 T1->>Counter: 总次数加一 T2->>Counter: 总次数加一 Note over Counter: 两次更新都生效,不会丢掉其中一次 T1->>Map: 没有该用户就创建,再把次数加一 T2->>Map: 同一次原子操作更新同一用户 Note over Map: 先查再放要合成一步,避免后写入覆盖先写入

Java 例程

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class SafeClasses {
    static final AtomicInteger HITS = new AtomicInteger(); // 总次数。incrementAndGet 不会丢更新
    static final ConcurrentHashMap<String, AtomicInteger> BY_USER =
            new ConcurrentHashMap<String, AtomicInteger>(); // 分段并发,读多写多时不必锁整张表

    static void hit(String user) {
        HITS.incrementAndGet();
        // computeIfAbsent 把“没有就创建”做成一次原子操作。
        // 如果写成先 get 再 put,两个线程可能各放进一个计数器,其中一个被覆盖。
        BY_USER.computeIfAbsent(user, k -> new AtomicInteger()).incrementAndGet();
    }
}

Python 例程

import threading
from queue import Queue

hits = 0
by_user = {}                      # 普通 dict 不是线程安全的,写它必须加锁
lock = threading.Lock()
tasks = Queue()                   # Queue 自己有锁,put/get 不需要再包一层

def hit(user):
    global hits
    with lock:                    # 总次数和分用户次数要一起改,所以放在同一临界区
        hits += 1
        by_user[user] = by_user.get(user, 0) + 1

def worker():
    while True:
        user = tasks.get()        # 队列空时,这个线程在这里休眠
        if user is None:          # None 是约定的结束标记,不是业务数据
            break
        hit(user)
        tasks.task_done()         # 和一次 get 配对,供 join 统计未完成任务