java 集群 ----1

java

最重要的是在集群中共享一个计数器,从而选择去连接那个数据源

-----------------------1

   首先来学习一下一个高并发性能的Map.ConcurrentHashMap

       ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。

          HashEntry 用来封装映射表的键 / 值对;
     Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。
   每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。

       (补充:一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中)

如图所示结构:插入了三个节点:

                 ConcurrentHashMap 在默认并发级别会创建包含 16 个 Segment 对象的数组。每个 Segment 的成员对象 table 包含若干个散列表的桶。每个桶是由 HashEntry 链接起来的一个链表。如果键能均匀散列,每个 Segment 大约守护整个散列表中桶总数的 1/16。  

 

          在 ConcurrentHashMap 中,线程对映射表做读操作时,一般情况下不需要加锁就可以完成,对容器做结构性修改的操作才需要加锁。下面以 put 操作为例说明对 ConcurrentHashMap 做结构性修改的过程。

首先,根据 key 计算出对应的 hash 值:

清单 4.Put 方法的实现

public V put(K key, V value) {

if (value == null) //ConcurrentHashMap 中不允许用 null 作为映射值

throw new NullPointerException();

int hash = hash(key.hashCode()); // 计算键对应的散列码

// 根据散列码找到对应的 Segment

return segmentFor(hash).put(key, hash, value, false);

}

 

然后,根据 hash 值找到对应的Segment 对象:


清单 5.根据 hash 值找到对应的 Segment

/**

* 使用 key 的散列码来得到 segments 数组中对应的 Segment

*/

final Segment<K,V> segmentFor(int hash) {

// 将散列值右移 segmentShift 个位,并在高位填充 0

// 然后把得到的值与 segmentMask 相“与”

// 从而得到 hash 值对应的 segments 数组的下标值

// 最后根据下标值返回散列码对应的 Segment 对象

return segments[(hash >>> segmentShift) & segmentMask];

}

 

最后,在这个 Segment 中执行具体的 put 操作:


清单 6.在 Segment 中执行具体的 put 操作

V put(K key, int hash, V value, boolean onlyIfAbsent) {

lock(); // 加锁,这里是锁定某个 Segment 对象而非整个 ConcurrentHashMap

try {

int c = count;

if (c++ > threshold) // 如果超过再散列的阈值

rehash(); // 执行再散列,table 数组的长度将扩充一倍

HashEntry<K,V>[] tab = table;

// 把散列码值与 table 数组的长度减 1 的值相“与”

// 得到该散列码对应的 table 数组的下标值

int index = hash & (tab.length - 1);

// 找到散列码对应的具体的那个桶

HashEntry<K,V> first = tab[index];

HashEntry<K,V> e = first;

while (e != null && (e.hash != hash || !key.equals(e.key)))

e = e.next;

V oldValue;

if (e != null) { // 如果键 / 值对以经存在

oldValue = e.value;

if (!onlyIfAbsent)

e.value = value; // 设置 value 值

}

else { // 键 / 值对不存在

oldValue = null;

++modCount; // 要添加新节点到链表中,所以 modCont 要加 1

// 创建新节点,并添加到链表的头部

tab[index] = new HashEntry<K,V>(key, hash, first, value);

count = c; // 写 count 变量

}

return oldValue;

} finally {

unlock(); // 解锁

}

}

 

这里的加锁操作是针对(键的 hash 值对应的)某个具体的 Segment,锁定的是该 Segment 而不是整个 ConcurrentHashMap。因为插入键 / 值对操作只是在这个 Segment 包含的某个桶中完成,不需要锁定整个ConcurrentHashMap。此时,其他写线程对另外 15 个Segment 的加锁并不会因为当前线程对这个 Segment 的加锁而阻塞。同时,所有读线程几乎不会因本线程的加锁而阻塞(除非读线程刚好读到这个 Segment 中某个 HashEntry 的 value 域的值为 null,此时需要加锁后重新读取该值)。

相比较于 HashTable 和由同步包装器包装的 HashMap每次只能有一个线程执行读或写操作,ConcurrentHashMap 在并发访问性能上有了质的提高。在理想状态下,ConcurrentHashMap 可以支持 16 个线程执行并发写操作(如果并发级别设置为 16),及任意数量线程的读操作。

以上是 java 集群 ----1 的全部内容, 来源链接: utcz.com/z/390438.html

回到顶部