有下面两个类:
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
。不过,迭代器被设计成每次仅由一个线程使用。