1.ConcurrentHashMap

  ConcurrentHashMap是线程安全的HashMap的实现。

  1)添加

  put(Object key , Object value)

  ConcurrentHashMap并没有采用synchronized进行控制,而是使用了ReentrantLock。

  public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
int hash = hash(key.hashCode());
return segmentFor(hash).put(key, hash, value, false);
}

  这里计算出key的hash值,根据hash值获取对应的数组中的segment对象。接下来的工作都交由segment完成。

  segment可以看成是HashMap的一个部分,(ConcurrentHashMap基于concurrencyLevel划分出了多个segment来对key-value进行存储)每次操作都只对当前segment进行锁定,

  从而避免每次put操作锁住整个map。

  

  V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = 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;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}

  这个方法进来就上锁(lock),并在finally中确保释放锁(unlock)。

  添加key-value的过程中,先判断当前存储对象个数加1后是否大于threshold,如果大于则进行扩容(对象数组扩大两倍,进行重新hash,转移到新数组)。

  如果不大于,则进行后续操作。通过对hash值和对象数组大小减1的值进行按位与操作(取余),得到当前key需要放入数组的位置,接着寻找对应位置上的hashEntry对象链表,并进行遍历。

  如果找到相同key值的Entry,则替换该Entry对象的value。

  如果没有找到就创建一个Entry对象,赋值给对应位置的数组对象,并构成链表。

  注意:采用segment这种方式,在并发操作过程中,可以在很多程度上减少阻塞现象。

  

  2)删除 remove(Object key)

  public V remove(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).remove(key, hash, null);
}

  和put类似,删除也要根据hash先获得segment,然后在segment上执行remove操作。

  V remove(Object key, int hash, Object value) {
lock();
try {
int c = count - 1;
HashEntry<K,V>[] tab = 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 = null;
if (e != null) {
V v = e.value;
if (value == null || value.equals(v)) {
oldValue = v;
// All entries following removed node can stay
// in list, but all preceding ones need to be
// cloned.
++modCount;
HashEntry<K,V> newFirst = e.next;
for (HashEntry<K,V> p = first; p != e; p = p.next)
newFirst = new HashEntry<K,V>(p.key, p.hash,
newFirst, p.value);
tab[index] = newFirst;
count = c; // write-volatile
}
}
return oldValue;
} finally {
unlock();
}
}

  segment的remove操作,首先加锁,然后对hash值与数组大小减1的值按位与操作,得到数组对应位置上的HashEntry对象,接下来遍历此链表,

  查找hash值相等并且key相等(equals)的对象。

  如果没有找到,返回null,释放锁。

  如果找到了,则重新创建位于删除元素之前的所有HashEntry,位于其后的不用处理。释放锁!

  

  3)获取 get(Object key)

  直接看看segment中的get操作,如下:

  V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry<K,V> e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}

  可以看出并没有加锁操作,只有v==null时,进入readValueUnderLock才有加锁操作。

  这里假设一种情况,例如两条线程a、b,a执行get操作,b执行put操作。

  当a执行到getFirst,与当前数组长度减1按位与操作后得到指定位置index,此时cpu将执行权交给b,b线程put一对key-value,

  导致扩容并重新hash排列,然后cpu又将执行权还给a,a然后根据之前的index去获取HashEntry就会发生问题。

  当然这种情况发生的概率很小。

  

  ConcurrentHashMap默认情况下采用将数据分为16个段进行存储,并且每个段各自拥有自己的锁,锁仅用于put和remove等改变集合对象的操作,基于voliate及hashEntry链表的不变性实现读取的不加锁。

  这些方式使得ConcurrentHashMap能够保持极好的并发操作,尤其是对于读远比插入和删除频繁的map而言,而它采用的这些方法也可谓是对于java内存模型、并发机制深刻掌握的体现,

  是一个设计得非常不错的支持高并发的集合对象。

2.CopyOnWriteArrayList

  CopyOnWriteArrayList是一个线程安全、并且在读操作时无锁的ArrayList。

  

  1)添加 add(E e)

  public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//复制数组
newElements[len] = e;//添加到末尾
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

  这里同样没有使用synchronized关键字,而是使用ReentrantLock。

  和ArrayList不同的是,这里每次都会创建一个新的object数组,大小比之前数组大1。将之前的数组复制到新数组,并将新加入的元素加到数组末尾。

  2)删除 remove(Object o)

public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// Copy while searching for element to remove
// This wins in the normal case of element being present
int newlen = len - 1;
Object[] newElements = new Object[newlen];//新建数组 for (int i = 0; i < newlen; ++i) {
if (eq(o, elements[i])) {
// found one; copy remaining and exit
for (int k = i + 1; k < len; ++k)
newElements[k-1] = elements[k];
setArray(newElements);
return true;
} else
newElements[i] = elements[i];
} // special handling for last cell
if (eq(o, elements[newlen])) {
setArray(newElements);
return true;
}
}
return false;
} finally {
lock.unlock();
}
}

  

  3)获取 get(int index)

  public E get(int index) {
return (E)(getArray()[index]);
}

  从上可见,CopyOnWriteArrayList基于ReentrantLock保证了增加元素和删除元素动作的互斥。在读操作上没有任何锁,这样就保证了读的性能,带来的副作用是有时候可能会读取到脏数据。

3.ArrayBlockingQueue

  ArrayBlockingQueue是一个基于数组、先进先出、线程安全的集合类,其特点是实现指定时间的阻塞读写,并且容量是可以限制的。

  1)创建

  public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = (E[]) new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}

  初始化锁和两个锁上的Condition,一个为notEmpty,一个为notFull。

  2)添加 offer(E e , long timeout , TimeUtil unit)

  public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException { if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
if (count != items.length) {
insert(e);
return true;
}
if (nanos <= 0)
return false;
try {
nanos = notFull.awaitNanos(nanos);
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
}
} finally {
lock.unlock();
}
}

  这个方法将元素插入数组的末尾,如果数组满,则进入等待,只到以下三种情况发生才继续:

  被唤醒、达到指定的时间、当前线程被中断。

  该方法首先将等待时间转换成纳秒。然后加锁,如果数组未满,则在末尾插入数据,

  如果数组已满,则调用notFull.awaitNanos进行等待。如果被唤醒或超时,重新判断是否满。如果线程被interrupt,则直接抛出异常。

Java并发包concurrent类简析的更多相关文章

  1. Java并发包concurrent——ConcurrentHashMap

    转: Java并发包concurrent——ConcurrentHashMap 2018年07月19日 20:43:23 Bill_Xiang_ 阅读数 16390更多 所属专栏: Java Conc ...

  2. SpringMVC源码情操陶冶-DispatcherServlet类简析(一)

    阅读源码有利于陶冶情操,此文承接前文SpringMVC源码情操陶冶-DispatcherServlet父类简析 注意:springmvc初始化其他内容,其对应的配置文件已被加载至beanFactory ...

  3. JAVA里的CAS算法简析

    Atomic 从JDK5开始, java.util.concurrent包里提供了很多面向并发编程的类. 使用这些类在多核CPU的机器上会有比较好的性能.主要原因是这些类里面大多使用(失败-重试方式的 ...

  4. Java中 方法的多态 简析图

    代码如下: public class Client{    public static void main(String[] args){        Person p = new Person() ...

  5. SpringMVC源码情操陶冶-DispatcherServlet简析(二)

    承接前文SpringMVC源码情操陶冶-DispatcherServlet类简析(一),主要讲述初始化的操作,本文将简单介绍springmvc如何处理请求 DispatcherServlet#doDi ...

  6. [Spring] 学习Spring Boot之一:基本使用及简析

    一.简介 使用 Spring Boot 目的主要是用来简化 Spring 应用的搭建及开发过程,因为使用 Spring 及 SpringMVC 框架时需要手动配置的地方非常多(各种包之间的依赖.各种配 ...

  7. SpringMVC源码情操陶冶-DispatcherServlet父类简析

    阅读源码有助于陶冶情操,本文对springmvc作个简单的向导 springmvc-web.xml配置 <servlet> <servlet-name>dispatch< ...

  8. Java集合及concurrent并发包总结(转)

    Java集合及concurrent并发包总结(转)   1.集合包 集合包最常用的有Collection和Map两个接口的实现类,Colleciton用于存放多个单对象,Map用于存放Key-Valu ...

  9. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

随机推荐

  1. [SinGuLaRiTy] KM算法

    [SinGuLaRiTy-1018] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. Some Method Are Reproduced F ...

  2. 【火车出栈】ZOJ - 2603 Railroad Sort

    好久没写递归了,怕手生再来练练手. 题意:车轨上有上图所示的n个中转栈,现有2n个列车,给出列车初始编号序列.列车从最右边驶入车轨,并且列车只能从右向左移动,要求给出列车中转操作序列,使列车经过这n个 ...

  3. Linux之rsync数据同步服务

    rsync是一个开源.快速的.多动能的.可以实现全量,增量的本地或远程数据同步备份工具,它适用于多种操作系统平台. 1.rsync的特性(功能) (1)支持拷贝特殊文件(如链接文件.设备文件) (2) ...

  4. Gym - 101845K 排序+概率

    The UNAL programming coaches have lost a bet, they bet the 6 UNAL teams would occupy the first six p ...

  5. SQL 模糊查询 可以正则匹配 (转)

    1. % 表示任意0个或多个字符.如下语句:Select * FROM user Where name LIKE '%三%'; 将会把name为“张三”,“三脚猫”,“唐三藏”等等有“三”的全找出来. ...

  6. PAT天梯赛L1-054 福到了

    题目链接:点击打开链接 "福"字倒着贴,寓意"福到".不论到底算不算民俗,本题且请你编写程序,把各种汉字倒过来输出.这里要处理的每个汉字是由一个 N x N 的 ...

  7. centos上安装docker

    一 docker安装: 1 首先需要检查linux内核的版本,docker要求linux内核是在3.10之上的, uname -r 2 更新yum源,注意这步应该是管理员权限,如果当前不是管理员,切换 ...

  8. zookeeper客户端使用第三方(zkclient)封装的Api操作节点

    1.引入依赖 <dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</a ...

  9. Python语言、编译解释、动态库静态库、编译过程、头文件

    学习Python这门语言首先要了解 什么是编译与解释,什么是连接,什么是动态库与静态库, 什么是编译: 编译就是先把高级语言设计的程序翻译成二进制的机器语言,然后CPU直接执行机器码就可以了.一把翻译 ...

  10. mongo察看replica set副本集primary主节点

    printjson(db.adminCommand( { isMaster: 1 } ));