关于springboot:记录本周问题

4次阅读

共计 3774 个字符,预计需要花费 10 分钟才能阅读完成。

我的项目中应用 hashMap

我的项目里两个中央都用到了 hashmap。然而感觉本人用的时候并没有感觉十分的清晰。同时发现 hashmap 有线程不平安问题,而本人用的时候就是多线程来应用。于是在这里介绍一下。

我的项目中两个中央用到了 hashmap。

1. 策略模式

一个是应用 hashmap 来贮存 service, 依据不同的事件调用不同的 service。

在构造函数中,把 service 增加到 map 中, 而后应用的时候,依据 eventName 获取相应的 service 进行解决。

@Service
1 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););
  }

  @Override
8  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();
    }

}
// 清空 value
hashMap.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 一对键值时,

  1. 依据 key 的 hashCode 值计算出一个地位,该地位就是此对象筹备往数组中寄存的地位。
  2. 如果该地位没有对象存在,就将此对象间接放进数组当中;
  3. 如果该地位曾经有对象存在了,则顺着此存在的对象的链开始寻找(为了判断是否是否值雷同,map 不容许 <key,value> 键值对反复)
  4. 进行尾插法,插入到链表前面。(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
//Hashtable
Map<String, String> hashtable = new Hashtable<>();
//synchronizedMap
Map<String, String> synchronizedHashMap = Collections.synchronizedMap(new HashMap<String, String>());
//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();

前两种是应用,synchronized 来保障线程平安的。最初一种是是 JUC 包中的一个类, 效率比拟高

正文完
 0