同步容器简介
针对容器我们知道有HashMap,HashTable,其中HashMap是一个非线程安全的,HashMap在并发执行put操作时会引起死循环,导致CPU利用率接近100%。因为多线程会导致HashMap的Node链表形成环形数据结构,一旦形成环形数据结构,Node的next节点永远不为空,就会在获取Node时产生死循环,而且死循环发生在发生在扩容时。
HashTable是线程安全的,但是在复合操作上会线程不安全的现象。
备注:
1)什么是复合操作?比如添加元素,需先判断是否元素存在,元素不存在时才添加。
2)hashtable是在插入和删除上都是索表的方式,因此效率特别低。
在jdk1.5之后提供java.util.concurrent包下的一些同步容器:java.util.concurrent.ConcurrentHashMap<K, V>。
ConcurrentHashMap性能讲解:
ConcurrentHashMap的性能要比HashTable性能高,而且高很多。
1)ConcurrentHashMap在jdk1.7之前采用了“锁分段”机制,默认把一个ConcurrentHashMap分为16个segment,每个segment下默认也是长度为16的哈希表,在每个哈希元素下是链表。这样就可默认每段有一个锁,一次可以最大并发操作16。
2)在jdk1.8之后ConcurrentHashMap采用CAS算法,可以把CAS算法错略的认为是无锁算法。
3)java.util.concurrent包还提供了设计用于多线程上下文中的Collection实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。
4)当希望许多线程访问一个给定的Collection时
a)ConcurrentHashMap由于同步的HashMap;
b)ConcurrentSkipListMap通常优于同步的TreeMap;
5)当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。
CopyOnWriteArrayList的用法示例:
测试ArrayList并发读写:
package com.dx.juc.test; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class CopyOnWriteTest { public static void main(String[] args) { new Thread(new MyCopyOnWrite()).start(); } } class MyCopyOnWrite implements Runnable { private static List<String> items = Collections.synchronizedList(new ArrayList<String>()); static { items.add("A"); items.add("B"); items.add("C"); } public void run() { for (String item : items) { System.out.println(item); items.add("D"); } } }
在执行时,抛出异常:
A Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at com.dx.juc.test.MyCopyOnWrite.run(CopyOnWriteTest.java:23) at java.lang.Thread.run(Thread.java:745)
如果使用CopyOnWriteArrayList时,并不会抛出异常,原理是每次在写入时,会在底层拷贝一份新的数据,因此如果是写入时性能不高。
package com.dx.juc.test; import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; public class CopyOnWriteTest { public static void main(String[] args) { MyCopyOnWrite myCopyOnWrite = new MyCopyOnWrite(); for (int i = 0; i < 5; i++) { new Thread(myCopyOnWrite).start(); } } } class MyCopyOnWrite implements Runnable { private static CopyOnWriteArrayList<String> items = new CopyOnWriteArrayList<String>(); static { items.add("A"); items.add("B"); items.add("C"); } public void run() { System.out.println(Thread.currentThread().getName() + ":start"); Iterator<String> iterator = items.iterator(); while (iterator.hasNext()) { System.out.println(Thread.currentThread().getName() + ":" + iterator.next()); items.add("D"); } System.out.println(Thread.currentThread().getName() + ":end"); } }