java线程中的一个小问题
程序员文章站
2022-04-20 14:49:39
...
有下面两个类:
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
。不过,迭代器被设计成每次仅由一个线程使用。