Java并发编程之并发容器ConcurrentHashMap(JDK1.7)解析

最近看了一下ConcurrentHashMap的相关代码,感觉JDK1.7和JDK1.8差别挺大的,这次先看下JDK1.7是怎么实现的吧

哈希(hash)

先了解一下啥是哈希(网上有很多介绍),是一种散列函数,简单来说就是将输入值转换为固定值的一种压缩映射,在Java中最常见的就是Object.hashCode(),通过固定算法计算出来的一个值

数据结构

ConcurrentHashMap主要结构是有Segment<K,V>以及HashEntry<K,V>链表组成的

我们先看一下HashEntry<K,V>的主要结构,还是单向链表的数据结构:

    static final class HashEntry<K,V> {final int hash;//hash值final K key;//存储keyvolatile V value;//存储值volatile HashEntry<K,V> next;//指向下一个,单向链表HashEntry(int hash, K key, V value, HashEntry<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}//......}

 再来看一下Segment<K,V>的数据结构,主要还是用到了HashEntry<K,V>数组:

static final class Segment<K,V> extends ReentrantLock implements Serializable {//数据储存数组transient volatile HashEntry<K,V>[] table;/*** The load factor for the hash table.  Even though this value* is same for all segments, it is replicated to avoid needing* links to outer object.* @serial*///扩容因子,当Segment的数量大于initialCapacity* loadFactor就会扩容final float loadFactor;/*** The table is rehashed when its size exceeds this threshold.* (The value of this field is always <tt>(int)(capacity ** loadFactor)</tt>.)*///阈值,超出后就必须重新散列,就是扩容transient int threshold;Segment(float lf, int threshold, HashEntry<K,V>[] tab) {this.loadFactor = lf;this.threshold = threshold;this.table = tab;}//.....
}

接下来看一下ConcurrentHashMap的构造函数以及相关变量:

    /*** The default initial capacity for this table,* used when not otherwise specified in a constructor.*///容器的默认大小static final int DEFAULT_INITIAL_CAPACITY = 16;/*** The default load factor for this table, used when not* otherwise specified in a constructor.*///用来调整大小的,就是扩容static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** The default concurrency level for this table, used when not* otherwise specified in a constructor.*///并发时访问的线程数量static final int DEFAULT_CONCURRENCY_LEVEL = 16;    final Segment<K,V>[] segments;//数据存储的数组//最大并发的线程数,不能超过65536static final int MAX_SEGMENTS = 1 << 16; // slightly conservative//最大容量数,不能超过2的30次方static final int MAXIMUM_CAPACITY = 1 << 30;public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (concurrencyLevel > MAX_SEGMENTS)concurrencyLevel = MAX_SEGMENTS;// Find power-of-two sizes best matching argumentsint sshift = 0;int ssize = 1;while (ssize < concurrencyLevel) {++sshift;ssize <<= 1;}this.segmentShift = 32 - sshift;this.segmentMask = ssize - 1;if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;int c = initialCapacity / ssize;if (c * ssize < initialCapacity)++c;int cap = MIN_SEGMENT_TABLE_CAPACITY;while (cap < c)cap <<= 1;// create segments and segments[0]Segment<K,V> s0 =new Segment<K,V>(loadFactor, (int)(cap * loadFactor),(HashEntry<K,V>[])new HashEntry[cap]);Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]this.segments = ss;}

在构造方法中可以看到,其实还是创建一个Segment的数组,默认的话长度为16,并且将s0变量赋值进去,s0中的HashEntry数组的大小默认为2。

接下来看一下我们经常用put()方法,源代码如下:

首先需要计算key值的hash值,计算方法是固定的算法,然后判断Segment数组中是否有这个hash值的数据,如果不存在的话,则进入扩容方法ensureSegment(j);在这个方法中可以看到扩容新数组的长度为table.length * loadFactor,即每次扩容为initialCapacity* loadFactor,只会扩容HashEntry数组,并非Segment数组;如果存在的话,则调用Segment的put()方法,这个方法总共有四个参数,最后一个参数是用于区别putIfAbsent()以及put(),这两个方法区别简单来说就是,判断当前key存不存在,如果存在的话put()方法就是覆盖,而putIfAbsent()就是不覆盖,并且这两个方法都会返回旧值,在下面的有Segment的put方法解析。

    @SuppressWarnings("unchecked")public V put(K key, V value) {Segment<K,V> s;if (value == null)throw new NullPointerException();int hash = hash(key);int j = (hash >>> segmentShift) & segmentMask;if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck(segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegments = ensureSegment(j);return s.put(key, hash, value, false);}private int hash(Object k) {int h = hashSeed;if ((0 != h) && (k instanceof String)) {return sun.misc.Hashing.stringHash32((String) k);}h ^= k.hashCode();// Spread bits to regularize both segment and index locations,// using variant of single-word Wang/Jenkins hash.h += (h <<  15) ^ 0xffffcd7d;h ^= (h >>> 10);h += (h <<   3);h ^= (h >>>  6);h += (h <<   2) + (h << 14);return h ^ (h >>> 16);}//扩容Segment的数组,private Segment<K,V> ensureSegment(int k) {final Segment<K,V>[] ss = this.segments;long u = (k << SSHIFT) + SBASE; // raw offsetSegment<K,V> seg;if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {Segment<K,V> proto = ss[0]; // use segment 0 as prototypeint cap = proto.table.length;float lf = proto.loadFactor;int threshold = (int)(cap * lf);HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) { // recheckSegment<K,V> s = new Segment<K,V>(lf, threshold, tab);while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))== null) {if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))break;}}}return seg;}//Segement中的put方法:可以看到,首先会先去获取锁final V put(K key, int hash, V value, boolean onlyIfAbsent) {HashEntry<K,V> node = tryLock() ? null :scanAndLockForPut(key, hash, value);V oldValue;try {HashEntry<K,V>[] tab = table;int index = (tab.length - 1) & hash;HashEntry<K,V> first = entryAt(tab, index);for (HashEntry<K,V> e = first;;) {//循环链表上的节点判断if (e != null) {K k;if ((k = e.key) == key ||(e.hash == hash && key.equals(k))) {oldValue = e.value;//返回旧值if (!onlyIfAbsent) {e.value = value;//如果是putIfAbsent()则不执行这段覆盖代码++modCount;}break;}e = e.next;//链表的下一个节点}else {//如果在对应的table数组中不存在则创建一个HashEntry节点,或者创建一个if (node != null)node.setNext(first);elsenode = new HashEntry<K,V>(hash, key, value, first);int c = count + 1;if (c > threshold && tab.length < MAXIMUM_CAPACITY)rehash(node);elsesetEntryAt(tab, index, node);++modCount;count = c;oldValue = null;break;}}} finally {unlock();//释放锁}return oldValue;

接下来看看get()方法,其实get()方法的现对来说较为简单,在定位segment和定位table后,依次扫描这个table元素下的的链表,要么找到元素,要么返回null。这里可能会有个并发问题如何获取是最新的,因为在HashEntry设计当中value属性的使用了 volatile保证了数据的可见性。但是在获取的时候并未上锁,所以在使用get()以及containsKey()方法会存在一致性问题,由于HashEntry是链表结构,所以在并发情况下如果其他线程进行修改HashEntry链表值的话(即会修改链表结构,导致链表的next节点地址错乱),返回值并非是实时数据。

    public V get(Object key) {Segment<K,V> s; // manually integrate access methods to reduce overheadHashEntry<K,V>[] tab;int h = hash(key);long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&(tab = s.table) != null) {for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next) {K k;if ((k = e.key) == key || (e.hash == h && key.equals(k)))return e.value;}}return null;}//获取containsKey的值public boolean containsKey(Object key) {Segment<K,V> s; // same as get() except no need for volatile value readHashEntry<K,V>[] tab;int h = hash(key);long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&(tab = s.table) != null) {for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);e != null; e = e.next) {K k;if ((k = e.key) == key || (e.hash == h && key.equals(k)))return true;}}return false;}
//所以在使用key为Object的时候需要重写一下equals以及hashCode方法

在使用size()时候,会进去两次统计,并且不是加锁统计,两次一致直接返回结果,不一致,重新加锁再次统计

public int size() {// Try a few times to get accurate count. On failure due to// continuous async changes in table, resort to locking.final Segment<K,V>[] segments = this.segments;int size;boolean overflow; // true if size overflows 32 bitslong sum;         // sum of modCountslong last = 0L;   // previous sumint retries = -1; // first iteration isn't retrytry {for (;;) {//第一次统计if (retries++ == RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)ensureSegment(j).lock(); // force creation}sum = 0L;size = 0;overflow = false;//第二次统计for (int j = 0; j < segments.length; ++j) {Segment<K,V> seg = segmentAt(segments, j);if (seg != null) {sum += seg.modCount;int c = seg.count;if (c < 0 || (size += c) < 0)overflow = true;}}if (sum == last)break;last = sum;}} finally {if (retries > RETRIES_BEFORE_LOCK) {for (int j = 0; j < segments.length; ++j)segmentAt(segments, j).unlock();}}return overflow ? Integer.MAX_VALUE : size;}

其他方法我就不介绍啦,下次再看一点JDK1.8的ConcurrentHashMap源代码,写的不是很好,不要见怪咯

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537167.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

带控制端的逻辑运算电路_分别完成正整数的平方、立方和阶乘的运算verilog语言...

练习&#xff1a;设计一个带控制端的逻辑运算电路&#xff0c;分别完成正整数的平方、立方和阶乘的运算。 //--------------myfunction---------- modulemyfunction(clk,n,result,reset,sl); output[6:0]result; input[2:0] n; input reset,clk; input [1:0] sl; reg[6:0]resul…

Java并发编程之并发容器ConcurrentHashMap(JDK1.8)解析

这个版本ConcurrentHashMap难度提升了很多&#xff0c;就简单的谈一下常用的方法就好了&#xff0c;可能有些讲的不太清楚&#xff0c;麻烦发现的大佬指正一下 主要数据结构 1.8将Segment取消了&#xff0c;保留了table数组的形式&#xff0c;但是不在以HashEntry纯链表的形式…

simulink显示多个数据_如何在 Simulink 中使用 PID Tuner 进行 PID 调参?

作者 | 安布奇责编 | 胡雪蕊出品 | CSDN(ID: CSDNnews)本文为一篇技术干货&#xff0c;主要讲述在Simulink如何使用PID Tuner进行PID调参。PID调参器( PIDTuner)概述1.1 简介使用PID Tuner可以对Simulink模型中的PID控制器&#xff0c;离散PID控制器&#xff0c;两自由度PID控制…

Java并发编程之堵塞队列介绍以及SkipList(跳表)

堵塞队列 先了解一下生产者消费者模式&#xff1a; 生产者就是生产数据的一方&#xff0c;消费者就是消费数据的另一方。在多线程开发中&#xff0c;如果生产者处理速度很快&#xff0c;而消费者处理速度很慢&#xff0c;那么生产者就必须等待消费者处理完&#xff0c;才能继…

python生成list的时候 可以用lamda也可以不用_python 可迭代对象,迭代器和生成器,lambda表达式...

分页查找#5.随意写一个20行以上的文件(divmod)# 运行程序&#xff0c;先将内容读到内存中&#xff0c;用列表存储。# l []# 提示&#xff1a;一共有多少页# 接收用户输入页码&#xff0c;每页5条&#xff0c;仅输出当页的内容def read_page(bk_list,n,endlineNone):startline …

数据挖掘技术简介[转]

关键词&#xff1a; 关键词&#xff1a;数据挖掘 数据集合 1. 引言  数据挖掘(Data Mining)是从大量的、不完全的、有噪声的、模糊的、随机的数据中提取隐含在其中的、人们事先不知道的、但又是潜在有用的信息和知识的过程。随…

树莓派安装smbus_树莓派使用smbus不兼容问题(no module named 'smbus')

树莓派使用smbus不兼容问题(no module named ‘smbus’)python3.5–3.6可以使用smbus2代替smbus1. 先参考以下方法&#xff1a;github讨论树莓派社区2.Pypi上可以下载smbus2smbus2PyPi介绍&#xff1a;当前支持的功能有&#xff1a;获取i2c功能(I2C_FUNCS)read_bytewrite_byter…

Java并发编程之线程池ThreadPoolExecutor解析

线程池存在的意义 平常使用线程即new Thread()然后调用start()方法去启动这个线程&#xff0c;但是在频繁的业务情况下如果在生产环境大量的创建Thread对象是则会浪费资源&#xff0c;不仅增加GC回收压力&#xff0c;并且还浪费了时间&#xff0c;创建线程是需要花时间的&…

面向过程的门面模式

{*******************************************************}{ }{ 业务逻辑一 }{ }{ 版权所有 (C) 2008 陈…

Java并发编程之线程定时器ScheduledThreadPoolExecutor解析

定时器 就是需要周期性的执行任务&#xff0c;也叫调度任务&#xff0c;在JDK中有个类Timer是支持周期性执行&#xff0c;但是这个类不建议使用了。 ScheduledThreadPoolExecutor 继承自ThreadPoolExecutor线程池&#xff0c;在Executors默认创建了两种&#xff1a; newSin…

python xml转换键值对_Python 提取dict转换为xml/json/table并输出

#!/usr/bin/python#-*- coding:gbk -*-#设置源文件输出格式import sysimport getoptimport jsonimport createDictimport myConToXMLimport myConToTabledef getRsDataToDict():#获取控制台中输入的参数&#xff0c;并根据参数找到源文件获取源数据csDict{}try:#通过getopt获取…

应用开发框架之——根据数据表中的存储的方法名称来调用方法

功用一&#xff1a;在框架里面根据存储在数据表中的方法名来动态调用执行方法。 unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 class(TForm) Button1: TButton; procedu…

Spring IOC容器组件注入的几种方式

整理一下之前Spring的学习笔记&#xff0c;大致有一下几种Spring注入到容器中的方法: 1&#xff09;、配置在xml的方式。 2&#xff09;、开启包扫描ComponentScan使用Component&#xff0c;Service&#xff0c;Controller&#xff0c;Repository&#xff08;其实后三个都继承…

我们是如何拿下Google和Facebook Offer的?

http://posts.careerengine.us/p/57c3a1c1a09633ee7e57803c 大家好&#xff0c;我是小高&#xff0c;CMU CS Master&#xff0c;来Offer第一期学员&#xff0c;2014年初在孙老师的带领下我在几个月的时间内进入了Yahoo&#xff0c;并工作了近2年。2016年初&#xff0c;Yahoo工作…

Spring中BeanFactory和FactoryBean的区别

先介绍一下Spring的IOC容器到底是个什么东西&#xff0c;都说是一个控制反转的容器&#xff0c;将对象的控制权交给IOC容器&#xff0c;其实在看了源代码之后&#xff0c;就会发现IOC容器只是一个存储单例的一个ConcurrentHashMap<String, BeanDefinition> BeanDefiniti…

python中数字和字符串可以直接相加_用c语言或者python将文件中特定字符串后面的数字相加...

匿名用户1级2014-08-31 回答代码应该不难吧。既然用爬虫爬下来了&#xff0c;为什么爬取数据的时候没做处理呢。之前用过Scrapy爬虫框架&#xff0c;挺好用的&#xff0c;你可研究下。代码&#xff1a;#!codingutf-8import osimport reimport random# 获取当前目录文件列表def …

Spring中Aware的用法以及实现

Aware 在Spring当中有一些内置的对象是未开放给我们使用的&#xff0c;例如Spring的上下文ApplicationContext、环境属性Environment&#xff0c;BeanFactory等等其他的一些内置对象&#xff0c;而在我们可以通过实现对应的Aware接口去拿到我们想要的一些属性&#xff0c;一般…

c#字符型转化为asc_C#字符串和ASCII码的转换

//字符转ASCII码&#xff1a;public static int Asc(string character){if (character.Length 1){System.Text.ASCIIEncoding asciiEncoding new System.Text.ASCIIEncoding();int intAsciiCode (int)asciiEncoding.GetBytes(character)[0];return (intAsciiCode);}else{thr…

topcoder srm 625 div1

problem1 link 假设第$i$种出现的次数为$n_{i}$&#xff0c;总个数为$m$&#xff0c;那么排列数为$T\frac{m!}{\prod_{i1}^{26}(n_{i}!)}$ 然后计算回文的个数&#xff0c;只需要考虑前一半&#xff0c;得到个数为$R$&#xff0c;那么答案为$\frac{R}{T}$. 为了防止数字太大导致…