序号:02
首先说明业务场景:
对公司数据的driver增加离线节点上线重连的功能。 大致功能需求,在线连接中有 节点1、节点2、节点3······,这时节点2离线了,将节点2移除在线连接池,加入离线节点池,然后定时访问离线节点池,尝试重新连接。由于driver连接自身没有保存连接的uri信息,所以我对driver连接自己封装了一层,大致如下:
原driver连接新建方式:
1
| Driver driver = XXX.driver(uri, db, username, password);
|
封装的Driver:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.xxx.xxx.xxx; import lombok.Data; import lombok.extern.slf4j.Slf4j;
@Slf4j @Data public class DriverAgent {
private String uri; private Driver driver; private String db; private String username; private String password; public DriverAgent () { } public DriverAgent (String uri, String db, String username, String password) { this.uri = uri; this.db= db; this.username = username; this.password = password; this.driver= xxx.driver(uri, db, username, password); } public Boolean reconnect(){ try { this.driver = xxx.driver(uri, db, username, password); log.info(uri + " has reconnected"); }catch (Exception e){ log.error("reconnection Failed:" + e.getMessage()); return false; } return true; } }
|
坑就出现在这里,首先这里我使用了lombok的@Data注解,它会生成以下方法:
- 所有属性的get和set方法
- toString 方法
- hashCode方法
- equals方法
注意,重写了hashCode方法和equals方法。
出现的问题:使用HashSet作为离线节点存储的容器,重连时使用迭代器遍历HashSet,调用reconnect方法,返回成功后调用iterator.remove()将当前节点从离线节点池中移除。但是测试过程中发现离线的节点重连成功后,iterator.remove()并未将其从HashSet中移除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| singleThreadExecutor.execute(() -> { while (true){ Iterator<DriverAgent> iterator = offlineNodes.iterator(); while (iterator.hasNext()){ DriverAgent agent = iterator.next(); if(agent.reconnect()){ agentList.add(agent); iterator.remove(); } } try { TimeUnit.SECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); } } });
|
经过阅读源码发现,HashSet实现借助的是HashMap,HashSet的Iterator获取的是HashMap.keySet().Iterator()。其返回的是一个EntryIterator,而EntryIterator继承自HashIterator,HashIterator的remove()方法是通过调用HashMap的removeNode方法实现的。
1 2 3 4 5 6 7 8 9 10 11
| public final void remove() { Node<K,V> p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; }
|
removeNode()方法源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }
|
可以看到,方法中会通过调用hashCode方法和equals方法去判断当前元素是不是要被删除的元素,而在前面,这两个方法是被重写过的,重写过程中,两个方法的返回值会与各个属性值挂钩,所以这里在我调用了reconnect方法后,如果通过hashCode和equals来比较两个类,其实已经不是同一个类了,因此导致iterator.remove()找不到要删除的元素。