天天看点

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

。不过,迭代器被设计成每次仅由一个线程使用。