最近开始学习java并发容器,以补充自己在并发方面的知识,从源码上进行。如有不正确之处,还请各位大神批评指正。

前言: 本人个人理解,看一个类的源码要先从构造器入手,然后再看方法。下面看BoundedConcurrentHashMap的注释

 1  先看HashTable的介绍:
    This class implements a hash table, which maps keys to values. Any non-<code>null</code> object can be used as a key or as a value.
2  该类实现了一个哈希表,它将键映射到值。任何非<code> null </ code>对象都可以用作键或值。
3  To successfully store and retrieve objects from a hashtable To successfully store and retrieve objects from a hashtable,
4  从哈希表中成功存储和检索对象从哈希表中成功存储和检索对象
5  the objects used as keys must implement the <code>hashCode</code> method and the <code>equals</code> method.
6  用作键的对象必须实现<code> hashCode </ code>方法和<code> equals </ code>方法
7  An instance of <code>Hashtable</code> has two parameters that affect its performance:
8  <code> Hashtable </ code>的一个实例有两个影响其*性能的参数:初始容量和负载系数<i>initial capacity</i> and <i>load factor</i>
9  The <i>capacity</i> is the number of <i>buckets</i> in the hash table, and the <i>initial capacity</i> is simply the capacity at the time the hash table is created
10  <i>容量</ i>是哈希表中<i>桶</ i>的数量,<i>初始容量</ i>只是创建哈希表时的容量
11  Note that the hash table is <i>open</i>: in the case of a "hash collision", a single bucket stores multiple entries, which must be searched sequentially.
12  请注意,哈希表是<i> open </ i>:在“哈希*冲突”的情况下,单个存储桶存储多个条目,必须按顺序搜索
13  The <i>load factor</i> is a measure of how full the hash table is allowed to get before its capacity is automatically increased
14  <i>加载因子</ i>衡量在其容量自动增加之前允许散列*表的填充程度 BoundedConcurrentHashMap类的

一个哈希表,支持检索的完全并发性和可更新的预期并发性。该类遵循与{@link java.util.Hashtable}相同的功能规范,并包含与 Hashtable 的每个方法对应的方法版本。但是,即使所有操作都是线程安全的,但检索操作不需要锁定,并且不支持以阻止所有访问的方式锁定整个表。在依赖于线程安全但不依赖于其同步细节的程序中,此类可与Hashtable完全互操作。检索操作(包括get)通常不会阻塞,因此可能与更新操作重叠(包括 put 和 remove )。检索反映了最近已完成更新操作的结果。对于诸如 putAll 和 clear 之类的聚合操作,并发检索可能反映仅插入或删除某些条目。他们不抛出{@link java.util.ConcurrentModificationException}。但是,迭代器设计为一次只能由一个线程使用。更新操作之间允许的并发性由可选的<tt> concurrencyLevel </ tt>构造函数参数(默认 16 )引导,该参数用作内部大小调整的提示。该表在内部进行分区,以尝试允许指定数量的并发更新而不会发生争用。因为散列表中的放置基本上是随机的,所以实际的并发性会有所不同。理想情况下,您应该选择一个值来容纳与同时修改表一样多的线程。使用比您需要的更高的值会浪费空间和时间,而显着更低的值可能导致线程争用。但是,在一个数量级内过高估计和低估通常不会产生明显的影响。当知道只有一个线程会修改而其他所有线程只能读取时,值为1是合适的。此外,调整此哈希表或任何其他类型的哈希表是一个相对较慢的操作,因此,在可能的情况下,最好在构造函数中提供预期表大小的估计值。该类及其视图和迭代器实现了{@link Map}和{@link Iterator}接口的所有<em>可选</ em>方法。本课程是从Infinispan复制的,最初由Doug Lea在JCP JSR-166专家组成员的协助下编写并发布到公共领域,如http://creativecommons.org/所述。licenses / publicdomain 与{@link java.util.Hashtable}类似,但与{@link HashMap}不同,此类不允许 null用作键或值。

默认初始容量: DEFAULT_MAXIMUM_CAPACITY = 512; 构造函数中未指定时使用
默认加载因子: DEFAULT_LOAD_FACTOR = 0.75f 构造函数中未指定时使用
默认的并发级别: DEFAULT_CONCURRENCY_LEVEL = 16 构造函数中未指定时使用
最大容量:MAXIMUM_CAPACITY = 1 << 30 当构造函数中指定时使用此参数,指定的值必须是2<= 1 << 30 的幂,以确保条目可使用整数进行索引。
允许的最大段数: MAX_SEGMENTS = 1 << 16 用于绑定*构造函数参数
不理解: RETRIES_BEFORE_LOCK = 2 、
segmentMask: 用于索引到段的掩码值,key的哈希码的高位用于选择段。
segmentShift: 移位值以在段内编制索引(不理解)
Segment<K, V>[] segments: 段。充当了分段锁的角色,这些段,每个段都是一个专门的哈希表。是这个类的内部类,继承了ReentrantLock只是为了简化一些锁定并避免单独构造
也具有keySet,entrySet, values
segmentFor(int hash): 根据传入的hash值来计算出该hash所在的段。
HashEntry: 内部类,用来封装散列映射表中的键值对,如果发生碰撞则采用“分离连接法”处理碰撞,把“碰撞”的 HashEntry 对象链接成一个链表。
由于 HashEntry 的 next 域为 final 型,所以新节点只能在链表的表头处插入

构造器: 其他的构造器也都间接的调用了这个

参数介绍

  capacity: 是该map中元素数量的上限容量

  concurrencyLevel:估计的并发更新线程数,该实现执行内部大小调整以尝试容纳这么多线程。其他几个构造器调用这个方法时传入的都是默认的 DEFAULT_CONCURRENCY_LEVEL = 16

  evictionStrategy:用于从此映射表中驱逐元素的算法(不太明白)

  evictionListener:驱逐监听器, 用来通知被驱逐的元素(不太明白)。

public BoundedConcurrentHashMap(int capacity, int concurrencyLevel,Eviction evictionStrategy, EvictionListener<K, V> evictionListener) {
  if ( capacity < 0 || concurrencyLevel <= 0 ) { // 不允许最大容量小于0或者并发更新线程数小于等于0,否则就没法玩了
  throw new IllegalArgumentException();
  }

  // 限制并发量不低于元素容量上限的一般并且不大于元传入的并发量

  concurrencyLevel = Math.min( capacity / 2, concurrencyLevel ); // concurrencyLevel cannot be > capacity/2
  concurrencyLevel = Math.max( concurrencyLevel, 1 ); // concurrencyLevel cannot be less than 1

  // 不允许最大容量小于并发数的两倍
  if ( capacity < concurrencyLevel * 2 && capacity != 1 ) {
  throw new IllegalArgumentException( "Maximum capacity has to be at least twice the concurrencyLevel" );
  }

  // 不允许驱逐算法和监听为空

  if ( evictionStrategy == null || evictionListener == null ) {
    throw new IllegalArgumentException();
  }

  // 限定最大并发更新线程数不大于MAX_SEGMENTS(是1<<16 = 65536,也即是2的16次幂,这样做就保证了最大并发是2的次幂数)

  if ( concurrencyLevel > MAX_SEGMENTS ) {
    concurrencyLevel = MAX_SEGMENTS;
  }

  // 用sshift 和 ssize变量来存储最佳匹配参数是2的次幂
  int sshift = 0;
  int ssize = 1;

  // 从后面看到ssize要用于创建的“分段锁”的数组长度,所以这里不能够比并发线程数小, 否则就会增加“线程”的竞争,导致效率下降。
  while ( ssize < concurrencyLevel ) {
    ++sshift;
    ssize <<= 1; // ssize<<=1 也即是 ssize = ssize * 2的1次幂,这里也即是取2的次幂
  }
  segmentShift = 32 - sshift; // 计算段移位量,用于与hash进行移位运算,找到hash所在的段的位置
  segmentMask = ssize - 1;
  this.segments = Segment.newArray( ssize ); // 初始化“分段锁”数组的长度。

  // 如果capacity(元素的容量上限)比规定的最大容量MAXIMUM_CAPACITY(1<<30)大,那么就去规定的最大容量

  if ( capacity > MAXIMUM_CAPACITY ) {
    capacity = MAXIMUM_CAPACITY;
  }
  int c = capacity / ssize; // 取元素的容量上限和“分段锁”数组的长度的余数
  int cap = 1;
  while ( cap < c ) { // 如果cap小于1那么对cap进行2的次幂运算,否则就把“分段锁”数组的每个元素中的数组初始容量定位1
    cap <<= 1;
  }

  for ( int i = 0; i < this.segments.length; ++i ) {

    // 从Segment构造器中可以看到cap是用来确定“分段锁”数组的具体元素中数组的长度,如果上面“分段锁”数组具体元素容量上限
    this.segments[i] = new Segment<K, V>( cap, c, DEFAULT_LOAD_FACTOR, evictionStrategy, evictionListener );
  }

  // 把Segment构造器放在这里只是配合上面循环中的new Segment容易理解。

  Segment(int cap, int evictCap, float lf, Eviction es, EvictionListener<K, V> listener) {
    loadFactor = lf;
    this.evictCap = evictCap;
    eviction = es.make( this, evictCap, lf );
    evictionListener = listener;
    setTable( HashEntry.<K, V>newArray( cap ) ); // 从这里可以看到cap是用来确定“分段锁”数组的具体元素中数组的长度
  }

  public V put(K key, V value) {
    if ( value == null ) { // 不允许value为null, 这里并没有对key做判断并不代表允许key为null,因为当key为null时在下面的key.hashCode()也会报出空指针异常
      throw new NullPointerException();
    }
    int hash = hash( key.hashCode() ); // 对key的hash码进行再hash确定“分段锁”数组中具体的数组下标

    // segmentFor(hash)是定位“分段锁数组”, Segment是一个内部类。
    return segmentFor( hash ).put( key, hash, value, false );
  }

  V put(K key, int hash, V value, boolean onlyIfAbsent) {
    lock(); // 加锁
    Set<HashEntry<K, V>> evicted = null;
    try {

      // count是此“分段锁”中的元素数
      int c = count;

      // threshold: 是“分段锁”中元素数量的阈值,Eviction.NON是驱逐算法

      // 此判断的意思是当元素数量大于阈值时, 那么表将会被重新hash分配整理
      if ( c++ > threshold && eviction.strategy() == Eviction.NONE ) {
        rehash();
      }
    HashEntry<K, V>[] tab = table;
    int index = hash & tab.length - 1;
    HashEntry<K, V> first = tab[index]; // 拿到数组中的第一个元素,至于为什么这样算出index就是第一个元素,不太理解
    HashEntry<K, V> e = first;

    // 这个while循环的作用是: 查看put进来的key表中是否已经存在,如果存在就是根据key替换value而不是新增。
    while ( e != null && ( e.hash != hash || !key.equals( e.key ) ) ) {
      e = e.next;
    }

    V oldValue;
    if ( e != null ) { // e不为空时说明上面while的另外一个条件e.hash != hash || !key.equals(e.key) 不成立,也即是传入的key在hash表中已经存在,这时候就替换value即可。
      oldValue = e.value; // 记下原来的value值返回。
      if ( !onlyIfAbsent ) {
        e.value = value;
        eviction.onEntryHit( e );
      }
    }
    else { // 否则就是出入的key在hash表中不存在,需要新增

      oldValue = null;

      // modCount是hash表的更新数,用来记录表的更新(可以理解成“乐观锁”,比如size方法中使用是,进入方法时会建立与“分段锁”长度一致的数组来存储每个“分段锁”中hash表修改的次数,当统计计算长度的时候会再次统计一次,然后比较

      // 这两次的值是否一致,如果不一致说明统计数量期间有别的线程进行了数据更新,那么就加上锁重新统计)

      ++modCount;

      // 将加1之后的元素数量重新赋值回去

      count = c; // write-volatile

      if ( eviction.strategy() != Eviction.NONE ) {

        // 当驱逐算法不是NONE时,并且又元素数量又达到了驱逐上限,那么就用eviction本身的驱逐算法对元素进行驱逐,以容纳新的元素

        if ( c > evictCap ) {
        // remove entries;lower count
        evicted = eviction.execute(); // 驱逐元素
        // 重新读取驱逐元素之后的第一个的值
        first = tab[index];
      }
      // 添加一个新的元素放在首位,并将新添加的元素的next指向原来的第一个元素,这样就在链表的开头新增了一个新的节点。
      tab[index] = eviction.createNewEntry( key, hash, first, value );
      //不太理解下面的操作
      Set<HashEntry<K, V>> newlyEvicted = eviction.onEntryMiss( tab[index] ); 
      if ( !newlyEvicted.isEmpty() ) {
        if ( evicted != null ) {
        evicted.addAll( newlyEvicted );
      }
      else {
        evicted = newlyEvicted;
      }
     }
    }
    else { // 如果驱逐算法是NONE构建完链表返回。
      tab[index] = eviction.createNewEntry( key, hash, first, value );
    }
   }

    return oldValue;
  }
  finally {
    unlock(); // 解锁,能够让其他线程访问
    notifyEvictionListener( evicted );
  }
 }

}


java 并发容器一之BoundedConcurrentHashMap(基于JDK1.8)的更多相关文章

  1. java 并发容器一之ConcurrentHashMap(基于JDK1.8)

    上一篇文章简单的写了一下,BoundedConcurrentHashMap,觉得https://www.cnblogs.com/qiaoyutao/p/10903813.html用的并不多:今天着重写 ...

  2. Java并发编程系列-(5) Java并发容器

    5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...

  3. Java 并发系列之六:java 并发容器(4个)

    1. ConcurrentHashMap 2. ConcurrentLinkedQueue 3. ConcurrentSkipListMap 4. ConcurrentSkipListSet 5. t ...

  4. 《Java并发编程的艺术》第6/7/8章 Java并发容器与框架/13个原子操作/并发工具类

    第6章 Java并发容器和框架 6.1  ConcurrentHashMap(线程安全的HashMap.锁分段技术) 6.1.1 为什么要使用ConcurrentHashMap 在并发编程中使用Has ...

  5. 死磕Java之聊聊HashSet源码(基于JDK1.8)

    HashSet的UML图 HashSet的成员变量及其含义 public class HashSet<E> extends AbstractSet<E> implements ...

  6. 死磕Java之聊聊HashMap源码(基于JDK1.8)

    死磕Java之聊聊HashMap源码(基于JDK1.8) http://cmsblogs.com/?p=4731 为什么面试要问hashmap 的原理

  7. java并发容器(Map、List、BlockingQueue)

    转发: 大海巨浪 Java库本身就有多种线程安全的容器和同步工具,其中同步容器包括两部分:一个是Vector和Hashtable.另外还有JDK1.2中加入的同步包装类,这些类都是由Collectio ...

  8. java并发容器(Map、List、BlockingQueue)具体解释

    Java库本身就有多种线程安全的容器和同步工具,当中同步容器包含两部分:一个是Vector和Hashtable.另外还有JDK1.2中增加的同步包装类.这些类都是由Collections.synchr ...

  9. Java并发容器--ConcurrentHashMap

    引子 1.不安全:大家都知道HashMap不是线程安全的,在多线程环境下,对HashMap进行put操作会导致死循环.是因为多线程会导致Entry链表形成环形数据结构,这样Entry的next节点将永 ...

随机推荐

  1. 在CentOs6.x 安装Cx_oracle5.x

    Setting up anything Oracle related is a huge pain. After hunting the web for info with minimal succe ...

  2. 「LuoguP3379」 【模板】最近公共祖先(LCA)

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  3. 自已封装Ajax方法

    function createXHR() { var request; if (typeof (XMLHttpRequest) == 'undefined') { request = new Acti ...

  4. 同步数据库bat脚本

    在开发中, 如果需要将测试数据库同步到本地进行本地的测试, 可以执行一下脚本进行同步 @ECHO off TITLE databaseBackup REM : 该脚本会将 191 的 database ...

  5. 1.1-1.4 sqoop概述及安装cdh版hadoop

    一.概述 Sqoop是一个在结构化数据和Hadoop之间进行批量数据迁移的工具,结构化数据可以是Mysql.Oracle等RDBMS. Sqoop底层用MapReduce程序实现抽取.转换.加载,Ma ...

  6. c/c++面试39-44之内存动态分配

    39 指针的初始化(二叉树排序),其中引入了双向链表 #include <stdio.h> #include <stdlib.h> struct Tag_Node { stru ...

  7. Flutter实战视频-移动电商-02.Flutter实战建立项目和编写入口文件

    02.Flutter实战建立项目和编写入口文件 创建项目: flutter create flutter_shop 创建完成之后呢,它会提示我们, 进入flutter_shop的目录,然后执行flut ...

  8. flex+blazeds

    BlazeDS开发指南: http://www.cnblogs.com/xia520pi/archive/2012/05/26/2519343.html 使用BlazeDS实现Flex和Java通信 ...

  9. Golang : flag 包简介

    在 Golang 程序中有很多种方法来处理命令行参数.简单的情况下可以不使用任何库,直接处理 os.Args:其实 Golang 的标准库提供了 flag 包来处理命令行参数:还有第三方提供的处理命令 ...

  10. numpy.ndarray常用属性和方法

    import numpy as np a = np.array([[1,2,3],[4,3,2],[6,3,5]])print(a) [[1 2 3] [4 3 2] [6 3 5]] print(a ...