1. [8.Condition.md](8.Condition.md) 这一节中,我们实现了一个 `BlockingQueue`(阻塞队列)。

BlockingQueue 的意思就是说,当一个线程调用这个 TaskQueuegetTask() 方法时,该方法内部可能会让线程变成等待状态,直到队列条件满足不为空,线程被唤醒后,getTask() 方法才会返回。
因为 BlockingQueue 非常有用,所以我们不必自己编写,可以直接使用 Java 标准库的 java.util.concurrent 包提供的线程安全的集合:ArrayBlockingQueue
除了 BlockingQueue 外,针对 ListMapSetDeque 等,java.util.concurrent 包也提供了对应的并发集合类:

| interface | non-thread-safe | thread-safe | | —- | —- | —- |

| List | ArrayList | CopyOnWriteArrayList |

| Map | HashMap | ConcurrentHashMap,ConcurrentSkipListMap |

| Set | HashSet / TreeSet | CopyOnWriteArraySet,ConcurrentSkipListSet |

| Queue | ArrayDeque / LinkedList | ArrayBlockingQueue / LinkedBlockingQueue |

| Deque | ArrayDeque / LinkedList | LinkedBlockingDeque |

还有一些没有列出来的并发集合类:PriorityBlockingQueue

ArrayBlockingQueue 有一个可选的参数来指定是否需要公平性。若设置了公平参数,则等待了最长时间的线程会优先得到处理。通常,公平性会降低性能,只有在确实非常需要时才使用它。

使用这些并发集合与使用非线程安全的集合类完全相同。我们以 ConcurrentHashMap 为例:

  1. Map<String, String> map = new ConcurrentHashMap<>();
  2. // 在不同的线程读写:
  3. map.put("A", "1");
  4. map.put("B", "2");
  5. map.get("A");

因为所有的同步和加锁的逻辑都在集合内部实现,对外部调用者来说,只需要正常按接口引用,其他代码和原来的非线程安全代码完全一样。
java.util.Collections 工具类还提供了一个旧的线程安全集合转换器,可以这么用:

  1. Map unsafeMap = new HashMap();
  2. Map threadSafeMap = Collections.synchronizedMap(unsafeMap);

但是它实际上是用一个包装类包装了非线程安全的 Map,然后对所有读写方法都用 synchronized 加锁,这样获得的线程安全集合的性能比 java.util.concurrent 集合要低很多,所以不推荐使用。
线程安全集合只能保证使用过程中不破坏数据结构。比如,在多线程中操作普通的 HashMap 会破坏内部的链表数组:有些连接可能丢失,或者甚至会构成循环,使得该数据结构不再可用。当线程集合类不会发生这样的情况。
但是使用线程安全集合类,并不一定就能让代码线程安全:

  1. Long oldValue = map.get(word); // map -> ConcurrentHashMap
  2. Long newValue = oldValue == null ? 1 : oldValue + 1;
  3. map.put(word, newValue);

上述代码中虽然使用 ConcurrentHashMap 类,但是由于上述操作并不是原子的,所以结果不可预知,线程不安全。

小结

使用 java.util.concurrent 包提供的线程安全的并发集合可以大大简化多线程编程:
多线程同时读写并发集合是安全的;
尽量使用 Java 标准库提供的并发集合,避免自己编写同步代码。