一、HashMap在非线程安全的环境下使用会出现什么样的问题?
public class HashMapMultiThread { static Map<String,String> map = new HashMap<String,String>(); public static class AddThread implements Runnable{ int start = 0 ; public AddThread(int start){ this.start = start; } @Override public void run() { for (int i = 0; i < 100000; i+=2) { map.put(Integer.toString(i), Integer.toBinaryString(i)); } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new HashMapMultiThread.AddThread(0)); Thread t2 = new Thread(new HashMapMultiThread.AddThread(1)); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(map.size()); } }
上述代码使用t1和t2两个线程同时对HashMap进行put()操作,如果一切正常,我们期望得到的map.size()就是100000.但实际上,你可能会得到以下三种情况(注意,这里使用JDK7进行试验):
第一:程序正常结束,并且结果也是符合预期的。HashMap的大小为100000.
第二:程序正常结束,但结果不符合预期,而是一个小于100000的数字,比如98868.
第三:程序永远无法结束。并发形成循环链表,导致死循环。
二、ConcurrentHashMap不能解决所有线程安全问题
对于ConcurrentHashMap,如果只调用get或put方法是线程安全的,但你调用get后再调用put之前,如果有另一个线程也调用了put就很可能把前面的操作结果覆盖了,因为违反了原则操作的规则。此时可用putIfAbsent方法代替。如下面的例子
public class ConcurrentHashMapTest { private static final ConcurrentMap<String, AtomicInteger> CACHE_MAP = new ConcurrentHashMap<>(); private static final String KEY = "test"; private static class TestThread implements Runnable{ @Override public void run() { if(CACHE_MAP.get(KEY)==null){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } CACHE_MAP.put(KEY, new AtomicInteger()); } CACHE_MAP.get(KEY).incrementAndGet(); } } public static void main(String[] args) { new Thread(new TestThread()).start(); new Thread(new TestThread()).start(); new Thread(new TestThread()).start(); try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("次数:"+CACHE_MAP.get(KEY).get()); } }