天天看點

java線程中的一個小問題

 有下面兩個類:

package Demo;

import java.util.HashMap;

public class HashMapTest{
	
	private HashMap<String, Integer> map = 
			new HashMap<String, Integer>();
	
	public synchronized void add(String key){
		Integer value = map.get(key);
		System.out.println("object1 -------- " +value);
		if(value == null){
			map.put(key, 1);
		}else{
			map.put(key, value+1);
		}
	}
	
}
           
package Demo;

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapTest {
	private ConcurrentHashMap<String, Integer> map = 
			new ConcurrentHashMap<String, Integer>();
	public void add(String key){
		Integer value = map.get(key);
		System.out.println("object2 -------- " + value);
		if(value == null){
			map.put(key, 1);
		}else{
			map.put(key, value+1);
		}
	}
}

           

 兩個都類中都有一個map容器對象,第一個類中容器為線程不安全的HashMap對象,第二類中為線程安全的ConcurrentHashMap 對象。同樣的也都有一個添加計數方法add,第一個類中是加了鎖synchronized,第二個中直接通路。

兩個類都是希望自己的資料容器map能夠在多線程的通路的情況下正常存取。

第一種使用了線程不安全容器加鎖的方式實作,第二種直接使用了線程安全容器進行通路。

下面是測試代碼:

package Demo;

public class ComputeObject implements Runnable{
	
	public static HashMapTest hashMapTest = new HashMapTest();
	public static ConcurrentHashMapTest concurrentHashMapTest = new ConcurrentHashMapTest();
	
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new Thread(new ComputeObject()).start();
		}
	}

	@Override
	public void run() {
		hashMapTest.add("key");
		concurrentHashMapTest.add("key");
	}
}
           

 主線程中開了10條子線程對兩個容器進行通路。

理想結果是:兩個容器除去第一次為空外,他們的中的屬性key值應當為9.

object1 -------- null
object2 -------- null
object1 -------- 1
object2 -------- 1
object1 -------- 2
object2 -------- 2
object1 -------- 3
object1 -------- 4
object2 -------- 3
object1 -------- 5
object2 -------- 4
object1 -------- 6
object2 -------- 5
object1 -------- 7
object2 -------- 5
object1 -------- 8
object2 -------- 6
object2 -------- 6
object1 -------- 9
object2 -------- 7
           

 可以看到,類一成功實作了多線程下的資料安全通路,然而類二中出現了大量的重複資料輸出。

 當時我們将類二中的add方法同樣加上synchronized方法,輸出兩個輸出都變為9。

這是因為對于ConcurrentHashMap中,它隻對put,remove操作使用了同步操作,get操作并不影響,這就可能在每次讀入的時候,讀入了相同的資料實作了重複的增加。

是以在使用ConcurrentHashMap時,應當保證足夠的小心。

jdkAPI:

擷取操作(包括 get)通常不會受阻塞,是以,可能與更新操作交疊(包括 put 和 remove)。擷取會影響最近完成的 更新操作的結果。對于一些聚合操作,比如 putAll 和 clear,并發擷取可能隻影響某些條目的插入和移除。類似地,在建立疊代器/枚舉時或自此之後,Iterators 和 Enumerations 傳回在某一時間點上影響哈希表狀态的元素。它們不會 抛出 

ConcurrentModificationException

。不過,疊代器被設計成每次僅由一個線程使用。