JAVA-JDK1.7-ConCurrentHashMap-测试和验证

概述

上次记录了关于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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2019-12-08 13:26  伯安知心  阅读(388)  评论(0编辑  收藏  举报

导航