多執行緒(十): 併發中集合ConcurrentHashMap
public class HashTest {
static Map<String, String> map = new HashMap<>();
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(() -> {
String key = Thread.currentThread().getName();
String value = String.valueOf(new Random().nextInt());
map.put(key, value);
System.out.println(key + "\t" + map.get(key));
}).start();
}
}
}
HashMap普通的遍歷方式在遍歷時是不允許刪除的
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<Integer, String>() {
{
put(0 , "星期一");
put(1, "星期二");
put(2, "星期三");
put(3, "星期四");
put(4, "星期五");
put(5, "星期六");
put(6, "星期日");
}
};
hashMap.forEach((key, value) -> {
if (key.intValue() <=4) hashMap.remove(key);
});
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap.forEach(HashMap.java:1291)
at com.company.example.hash.HashMapTest.main(HashMapTest.java:30)
使用迭代器方式在遍歷的時候可以刪除
public static void main(String[] args) {
Map<Integer, String> hashMap = new HashMap<Integer, String>() {
{
put(0, "星期一");
put(1, "星期二");
put(2, "星期三");
put(3, "星期四");
put(4, "星期五");
put(5, "星期六");
put(6, "星期日");
}
};
Iterator<Map.Entry<Integer, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> entry = iterator.next();
Integer key = entry.getKey();
if (key.intValue() <=4) iterator.remove();
}
// {5=星期六, 6=星期日}
System.out.println(hashMap);
}
ConcurrentHashMap: 使用普通的遍歷方式可以在遍歷的時候刪除操作
public static void main(String[] args) {
Map<Integer, String> hashMap = new ConcurrentHashMap<Integer, String>() {
{
put(0, "星期一");
put(1, "星期二");
put(2, "星期三");
put(3, "星期四");
put(4, "星期五");
put(5, "星期六");
put(6, "星期日");
}
};
hashMap.forEach((key, value) -> {
if (key.intValue() <=4) hashMap.remove(key);
});
System.out.println(hashMap);
}
ConcurrentHashMap是併發效率更高的Map,用來替換其他執行緒安全的Map容器,比如Hashtable和Collections.synchronizedMap。
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
public ConcurrentHashMap();
public ConcurrentHashMap(int initialCapacity);
public ConcurrentHashMap(Map<? extends K, ? extends V> m);
public V get(Object key);
public V put(K key, V value);
// 如果key對應的value不存在,則put進去,返回null。否則不put,返回已存在的value
public V putIfAbsent(K key, V value)
// 如果key對應的當前值是oldValue,則替換為newValue,返回true。否則不替換,返回false
public boolean replace(K key, V oldValue, V newValue);
// 如果key對應的值是value,則移除K-V,返回true。否則不移除,返回false
public V remove(Object key);
}
ConcurrentHashMap的簡要總結:
public V get(Object key)不涉及到鎖,也就是說獲得物件時沒有使用鎖;
put、remove方法要使用鎖,但並不一定有鎖爭用,原因在於ConcurrentHashMap將快取的變數分到多個Segment,每個Segment上有一個鎖,只要多個執行緒訪問的不是一個Segment就沒有鎖爭用,就沒有堵塞,各執行緒用各自的鎖,ConcurrentHashMap預設情況下生成16個Segment,也就是允許16個執行緒併發的更新而儘量沒有鎖爭用;
Iterator物件的使用,不一定是和其它更新執行緒同步,獲得的物件可能是更新前的物件,ConcurrentHashMap允許一邊更新、一邊遍歷,也就是說在Iterator物件遍歷的時候,ConcurrentHashMap也可以進行remove,put操作,且遍歷的資料會隨著remove,put操作產出變化,所以希望遍歷到當前全部資料的話,要麼以ConcurrentHashMap變數為鎖進行同步(synchronized該變數),要麼使用CopiedIterator包裝iterator,使其拷貝當前集合的全部資料,但是這樣生成的iterator不可以進行remove操作。
Hashtable和ConcurrentHashMap的不同點:
Hashtable對get,put,remove都使用了同步操作,它的同步級別是正對Hashtable來進行同步的,也就是說如果有執行緒正在遍歷集合,其他的執行緒就暫時不能使用該集合了,這樣無疑就很容易對效能和吞吐量造成影響,從而形成單點。而ConcurrentHashMap則不同,它只對put,remove操作使用了同步操作,get操作並不影響,詳情請看以上第1,2點,當前ConcurrentHashMap這樣的做法對一些執行緒要求很嚴格的程式來說,還是有所欠缺的,對應這樣的程式來說,如果不考慮效能和吞吐量問題的話,個人覺得使用Hashtable還是比較合適的;
Hashtable在使用iterator遍歷的時候,如果其他執行緒,包括本執行緒對Hashtable進行了put,remove等更新操作的話,就會丟擲ConcurrentModificationException異常,但如果使用ConcurrentHashMap的話,就不用考慮這方面的問題了,詳情請看以上第3點;
ConcurrentHashMap是執行緒安全的,但是ConcurrentHashMap只能保證自己是安全的,並不能保證其它業務邏輯是執行緒安全的
public static void main(String[] args) throws InterruptedException {
String key = "key";
Map<String, Integer> hashMap = new ConcurrentHashMap<>();
CountDownLatch countDownLatch = new CountDownLatch(2);
Runnable runnable = () -> {
for (int i = 0; i < 5; i++) {
Integer value = hashMap.get(key);
if (value == null) {
hashMap.put(key, 1);
} else {
hashMap.put(key, value + 1);
}
}
countDownLatch.countDown();
};
// 兩個執行緒操作同一個key,可能會出現覆蓋的現象,導致key的值小於10
new Thread(runnable).start();
new Thread(runnable).start();
countDownLatch.await();
System.out.println(hashMap);
}
CAS改進版
public static void main(String[] args) throws InterruptedException {
String key = "key";
Map<String, Integer> hashMap = new ConcurrentHashMap<>();
CountDownLatch countDownLatch = new CountDownLatch(2);
Runnable runnable = () -> {
Integer oldValue, newValue;
for (int i = 0; i < 5; i++) {
while (true) {
// CAS機制:如果put失敗,繼續獲取最新的值,然後繼續嘗試新增,直到put成功
oldValue = hashMap.get(key);
if (oldValue == null) {
newValue = 1;
// 如果key != newValue 則 hashMap.put(key, newValue)
if (hashMap.putIfAbsent(key, newValue) == null) {
break;
}
} else {
newValue = oldValue + 1;
// 如果key對應的當前值是oldValue,則替換為newValue,返回true。否則不替換,返回false
if (hashMap.replace(key, oldValue, newValue)) {
break;
}
}
}
}
countDownLatch.countDown();
};
new Thread(runnable).start();
new Thread(runnable).start();
countDownLatch.await();
System.out.println(hashMap);
}
public static void main(String[] args) {
String key = “key”;
Map
public static void main(String[] args) throws InterruptedException {
String key = "key";
Map<String, AtomicInteger> hashMap = new ConcurrentHashMap<>();
CountDownLatch countDownLatch = new CountDownLatch(2);
Runnable runnable = () -> {
AtomicInteger oldValue = null;
for (int i = 0; i < 5; i++) {
oldValue = hashMap.get(key);
if (oldValue == null) {
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t if");
AtomicInteger initValue = new AtomicInteger(0);
oldValue = hashMap.putIfAbsent(key, initValue);
if (oldValue == null) {
oldValue = initValue;
}
}
oldValue.incrementAndGet();
}
countDownLatch.countDown();
};
new Thread(runnable).start();
new Thread(runnable).start();
countDownLatch.await();
System.out.println(hashMap);
}
徹頭徹尾理解 ConcurrentHashMap: https://blog.csdn.net/justloveyou_/article/details/72783008