概述
上次记录了关于ConCurrentHashMap的原理以及源码,现在我对其进行有关功能的测试,下面就讲解一下我测试的内容和代码。这些测试主要针对JDK1.7版本。
GET安全测试
上一篇写过get方法是没有加锁的,因为HashEntry的value和next属性是volatile的,volatile直接保证了可见性,所以读的时候可以不加锁,现在写一个程序,启动20个线程,只有一个key="count",每个线程都要执行 map.put(key, 1) 或者 map.put(key, value + 1) ,所以理论上说,20个线程会得到值("count",20),所有的源码如下:
1 package test; 2 3 import java.io.Serializable; 4 import java.util.concurrent.ConcurrentHashMap; 5 import java.util.concurrent.ExecutorService; 6 import java.util.concurrent.Executors; 7 import java.util.concurrent.locks.ReentrantLock; 8 9 /** 10 * Created by Administrator on 2019/12/4. 11 */ 12 public class TestConcurrentHashMap { 13 14 15 public static void main(String[] args) { 16 DoWork dw = new DoWork(map); 17 //map.put("1",1); 18 ExecutorService pool = Executors.newFixedThreadPool(8); 19 try { 20 for (int i = 0; i < 20; i++) { 21 pool.execute(new Thread(dw));// 开启20个线程 22 } 23 Thread.sleep(5000);// 主线程睡眠5s 等待子线程完成任务 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } finally { 27 pool.shutdown();// 关闭线程池 28 } 29 System.out.println("统计的数量:" + map.get("count")); 30 31 } 32 private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>(); 33 34 static class DoWork extends ReentrantLock implements Serializable,Runnable { 35 36 private ConcurrentHashMap<String, Integer> map = null; 37 38 public DoWork(ConcurrentHashMap<String, Integer> map) { 39 this.map = map; 40 } 41 42 @Override 43 public void run() { 44 add("count"); 45 } 46 47 public void add(String key) { 48 Integer value = map.get(key);// 获取map中的数值 49 System.out.println("当前数量" + value); 50 if (null == value) { 51 map.put(key, 1);// 第一次存放 52 } else { 53 map.put(key, value + 1);// 以后次存放 54 } 55 } 56 public void addLock(String key) { 57 lock(); 58 try { 59 Integer value = map.get(key); 60 System.out.println("当前数量" + value); 61 if (null == value) { 62 map.put(key, 1); 63 } else { 64 map.put(key, value + 1); 65 } 66 } finally { 67 unlock(); 68 } 69 } 70 71 72 73 } 74 75 }
在如上测试代码中有如下问题:
问题1:为什么开启20个线程,启动的线程池确是8个 见第18行 21行代码?
回答:这样可以更大的概率实现线程并发。也就是更容易出现问题。
问题2:为什么要睡眠5s,
回答:那是因为统计结果的时候尽量确认所有线程执行完了,结果更加准确。
问题3:ReentrantLock 方法是干嘛的?
回答:ReentrantLock实现了独占功能,是这里使用的原因。
当在线程run方法中执行add(String key)方法时候,执行结果如下:
明显有问题的说,put是有锁的,所以先猜测get存在不安全的嫌疑,现在查看get方法源码,说明get确实没有加锁。所以说明的问题是虽然put有原子性,但是get+put就没有原子性,也就是说,当第一个线程get一个值之后,还没有put进去的时候,就被第二个线程get了,所以第二个线程get的值就是第一个线程put之前的值。
1 public V get(Object key) { 2 Segment<K,V> s; // manually integrate access methods to reduce overhead 3 HashEntry<K,V>[] tab; 4 int h = hash(key); 5 long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; 6 if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null && 7 (tab = s.table) != null) { 8 for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile 9 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); 10 e != null; e = e.next) { 11 K k; 12 if ((k = e.key) == key || (e.hash == h && key.equals(k))) 13 return e.value; 14 } 15 } 16 return null; 17 }
现在对add方法执行加锁方法,执行测试代码的56行代码的addLock方法。
然后debug输出是:
并发效率测试
现在模拟1000个并发,每个测试1000次操作,循环测试10轮。分别测试Put和Get操作,测试对象HashMapSync、ConcurrentHashMap、Hashtable。源码如下:
1 package test; 2 import java.util.Collections; 3 import java.util.HashMap; 4 import java.util.Hashtable; 5 import java.util.Map; 6 import java.util.concurrent.ConcurrentHashMap; 7 8 import static javafx.scene.input.KeyCode.T; 9 10 11 /** 12 * 测试HashMap和ConcurrentHashMap的并发性能差别。 13 * 14 * 15 */ 16 public class TestConCurrent { 17 static final int threads = 1000; 18 static final int NUMBER = 1000; 19 20 public static void main(String[] args) throws Exception { 21 Map<String, Integer> hashmapSync = Collections 22 .synchronizedMap(new HashMap<String, Integer>()); 23 Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<String, Integer>(); 24 Map<String, Integer> hashtable = new Hashtable<String, Integer>(); 25 long totalA = 0; 26 long totalB = 0; 27 long totalC = 0; 28 for (int i = 0; i <= 10; i++) { 29 totalA += testPut(hashmapSync); 30 totalB += testPut(concurrentHashMap); 31 totalC += testPut(hashtable); 32 } 33 System.out.println("Put time HashMapSync=" + totalA + "ms."); 34 System.out.println("Put time ConcurrentHashMap=" + totalB + "ms."); 35 System.out.println("Put time Hashtable=" + totalC + "ms."); 36 totalA = 0; 37 totalB = 0; 38 totalC = 0; 39 for (int i = 0; i <= 10; i++) { 40 totalA += testGet(hashmapSync); 41 totalB += testGet(concurrentHashMap); 42 totalC += testGet(hashtable); 43 } 44 System.out.println("Get time HashMapSync=" + totalA + "ms."); 45 System.out.println("Get time ConcurrentHashMap=" + totalB + "ms."); 46 System.out.println("Get time Hashtable=" + totalC + "ms."); 47 } 48 public static long testPut(Map<String, Integer> map) throws Exception { 49 long start = System.currentTimeMillis(); 50 for (int i = 0; i < threads; i++) { 51 new MapPutThread(map).start(); 52 } 53 while (MapPutThread.counter > 0) { 54 Thread.sleep(1); 55 } 56 return System.currentTimeMillis() - start; 57 } 58 public static long testGet(Map<String, Integer> map) throws Exception { 59 long start = System.currentTimeMillis(); 60 for (int i = 0; i < threads; i++) { 61 new MapGetThread(map).start(); 62 } 63 while (MapGetThread.counter > 0) { 64 Thread.sleep(1); 65 } 66 return System.currentTimeMillis() - start; 67 } 68 } 69 class MapPutThread extends Thread { 70 static int counter = 0; 71 static Object lock = new Object(); 72 private Map<String, Integer> map; 73 private String key = this.getId() + ""; 74 MapPutThread(Map<String, Integer> map) { 75 synchronized (lock) { 76 counter++; 77 } 78 this.map = map; 79 } 80 public void run() { 81 for (int i = 1; i <= TestConCurrent.NUMBER; i++) { 82 map.put(key, i); 83 } 84 synchronized (lock) { 85 counter--; 86 } 87 } 88 } 89 class MapGetThread extends Thread { 90 static int counter = 0; 91 static Object lock = new Object(); 92 private Map<String, Integer> map; 93 private String key = this.getId() + ""; 94 MapGetThread(Map<String, Integer> map) { 95 synchronized (lock) { 96 counter++; 97 } 98 this.map = map; 99 } 100 public void run() { 101 for (int i = 1; i <= TestConCurrent.NUMBER; i++) { 102 map.get(key); 103 } 104 synchronized (lock) { 105 counter--; 106 } 107 } 108 }
如上代码只是测试效率的简单测试:
测试结果是:
总结
能动手的尽量不要猜,能用代码的尽量不要相信原理。
感谢网络大神,参考链接:
https://blog.csdn.net/java2000_net/article/details/3373181
https://www.cnblogs.com/yanphet/p/5726919.html
来源:https://www.cnblogs.com/boanxin/p/11966629.html