我的项目中应用hashMap
我的项目里两个中央都用到了hashmap。然而感觉本人用的时候并没有感觉十分的清晰。同时发现hashmap有线程不平安问题,而本人用的时候就是多线程来应用。于是在这里介绍一下。
我的项目中两个中央用到了hashmap。
1.策略模式
一个是应用hashmap来贮存service, 依据不同的事件调用不同的service。
在构造函数中,把service增加到map中, 而后应用的时候,依据eventName获取相应的service进行解决。
@Service1 public class GitLabNotifyServiceImpl implements GitLabNotifyService {2 private Map<String, EventService> map = new HashMap<>();3 public GitLabNotifyServiceImpl(PushEventService pushEventService,4 IssueEventService issueEventService,5 CommentEventService) {6 this.addService(issueEventService);7 this.addService(pushEventService);); } @Override8 public void handleEventData(String json, String eventName,String access_token) throws IOException {9 EventService eventService = this.map.get(eventName); }
2.合并事件
开发背景:
gitlab中有评论并敞开的按钮
点击后钉钉机器人会发送两条告诉
于是就有需要是合并事件。
测试发现,总是评论事件优于issue事件发送。
实现逻辑
而后就写了个办法。
因为spring boot的bean默认都是单例的,所以定义了一个hashmap, 记录以后要执行的gitlab的事件
/** * key: access_token, github钉钉机器人的token * value GitlabEvent, gitlab事件 */ private static final HashMap<String, String> hashMap = new HashMap<>();
两个事件发送后,会有comment申请和issue close申请短距离被服务器承受。
服务器会开启两个线程。
两个线程都对hashmap进行拜访。
以后事件为comment事件的时候: 向hashMap中记录对应钉钉机器人事件是comment,线程先睡眠两秒,若后续有issue close事件,则合并事件。即不发送isssue close事件,并在comment事件上减少已敞开的提示信息。
以后事件为issue close事件的时候: 若2s前有comment事件,并且是同一个issue,则不发送issue close事件。
public String commentAndIssueClose(String json, String eventName, String access_token) throws IOException {if (Objects.equals(eventName, GitlabEvent.issueHook)) { // 若issue事件后面有comment事件,且为issue为close事件 则不发送该事件 if (Objects.equals(hashMap.get(access_token), GitlabEvent.noteHook) && this.judgeIsIssueClose(json)) { hashMap.put(access_token, GitlabEvent.issueHook); // 返回null 示意不发送 return null; }} else if (Objects.equals(eventName, GitlabEvent.noteHook)) { try { hashMap.put(access_token, GitlabEvent.noteHook); // 因为评论事件先于issue事件发送, 所以评论事件期待两秒 TimeUnit.SECONDS.sleep(2); // 若2s后存在issue事件 则事件合并 if (Objects.equals(hashMap.get(access_token), GitlabEvent.issueHook)) { hashMap.put(access_token, ""); return this.setIssueTittleClose(json); } } catch (InterruptedException e) { e.printStackTrace(); }}// 清空valuehashMap.put(access_token, "");return json;}
合并后果,后面加上已敞开:
HashMap线程不平安
尽管写完了能实现,然而找材料的时候,发现HashMap线程不平安。 就想着我也是多线程拜访,我这么写是不是会出bug。
先来说一下为什么hashMap的构造以及为什么线程不平安。
构造
HashMap是采纳“链表数组”的数据结构,即数组和链表的结合体(在JDK1.8中还引入了红黑树结构,当一个链表上的结点达到8时改为红黑树结构)。
HashMap底层保护一个数组,数组中的每一项都是一个Entry
transient Entry<K,V>[] table;
咱们向 HashMap 中所搁置的对象实际上是存储在该数组当中;
而Map中的key,value则以Entry的模式寄存在数组中
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash;
而这个Entry应该放在数组的哪一个地位上,是通过key的hashCode来计算的。
final int hash(Object k) { int h = 0; h ^= k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);}
通过hash计算出来的值将会应用indexFor办法找到它应该所在的table下标:
static int indexFor(int h, int length) { return h & (length-1);}
当两个key通过hashCode计算雷同时,则产生了hash抵触(碰撞),HashMap解决hash抵触的形式是用链表。
总结:
当向 HashMap 中 put 一对键值时,
- 依据 key的 hashCode 值计算出一个地位, 该地位就是此对象筹备往数组中寄存的地位。
- 如果该地位没有对象存在,就将此对象间接放进数组当中;
- 如果该地位曾经有对象存在了,则顺着此存在的对象的链开始寻找(为了判断是否是否值雷同,map不容许<key,value>键值对反复)
- 进行尾插法,插入到链表前面。(jdk1.8之前是头插法)
HashMap线程不平安的体现:
JDK1.7 HashMap线程不安整体当初:死循环、数据失落
JDK1.8 HashMap线程不安整体当初:数据笼罩
JDK1.7 中,因为多线程对HashMap进行扩容,调用了HashMap.transfer(),具体起因:某个线程执行过程中,被挂起,其余线程曾经实现数据迁徙,等CPU资源开释后被挂起的线程从新执行之前的逻辑,数据曾经被扭转,造成死循环、数据失落
。
JDK1.8 中,因为多线程对HashMap进行put操作,调用了HashMap.putVal(),具体起因:假如两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是雷同的,当线程A执行中后因为工夫片耗尽导致被挂起,而线程B失去工夫片后在该下标处插入了元素,实现了失常的插入,而后线程A取得工夫片,因为之前曾经进行了hash碰撞的判断,所有此时不会再进行判断,而是间接进行插入,这就导致了线程B插入的数据被线程A笼罩了,从而线程不平安。
改善:
数据失落、死循环曾经在在JDK1.8中曾经失去了很好的解决,因为JDK1.8间接在HashMap#resize()中实现了数据迁徙。
如何线程平安的应用HashMap
理解了 HashMap 为什么线程不平安,那当初看看如何线程平安的应用 HashMap。以下三种形式:
- Hashtable
- ConcurrentHashMap
- Synchronized Map
//HashtableMap<String, String> hashtable = new Hashtable<>();//synchronizedMapMap<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());//ConcurrentHashMapMap<String, String> concurrentHashMap = new ConcurrentHashMap<>();
前两种是应用,synchronized 来保障线程平安的。 最初一种是是 JUC 包中的一个类, 效率比拟高