java中并发包简要分析01
参考《分布式java应用》一书,简单过一遍并发包(java.util.concurrent)
ConcurrentHashMap
ConcurrentHashMap是线程安全的HashMap的实现。
1)添加
put(Object key , Object value)
ConcurrentHashMap并没有采用synchronized进行控制,而是使用了ReentrantLock。
public V if (valuenull) throw new NullPointerException(); int hash return segmentFor(hash).put(key,false); } |
这里计算出key的hash值,根据hash值获取对应的数组中的segment对象。接下来的工作都交由segment完成。
segment可以看成是HashMap的一个部分,(ConcurrentHashMap基于concurrencyLevel划分出了多个segment来对key-value进行存储)每次操作都只对当前segment进行锁定,从而避免每次put操作锁住整个map。
Vint hash,boolean onlyIfAbsent) lock(); try { int c if (c++// rehash(); HashEntry<K,V>[] int index1); HashEntry<K,V> HashEntry<K,V> while (enull && e V if (enull) oldValue if (!onlyIfAbsent) e.value } else { oldValuenull; ++modCount; tab[index]new HashEntry<K,V>(key, count// } 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 int hash return segmentFor(hash).remove(key,null); } |
和put类似,删除也要根据hash先获得segment,然后在segment上执行remove操作。
Vint hash, lock(); try { int c1; HashEntry<K,V>[] int index1); HashEntry<K,V> HashEntry<K,V> while (enull && e Vnull; if (enull) V if (valuenull || oldValue // // // ++modCount; HashEntry<K,V> for (HashEntry<K,V> newFirstnew HashEntry<K,V>(p.key, newFirst, tab[index] count// } } return oldValue; }finally { unlock(); } } |
segment的remove操作,首先加锁,然后对hash值与数组大小减1的值按位与操作,得到数组对应位置上的HashEntry对象,接下来遍历此链表,查找hash值相等并且key相等(equals)的对象。
如果没有找到,返回null,释放锁。
如果找到了,则重新创建位于删除元素之前的所有HashEntry,位于其后的不用处理。释放锁!
3)获取
get(Object key)
直接看看segment中的get操作,如下:
Vint hash) if (count0)// HashEntry<K,V> while (enull) if (e.hash V if (vnull) return v; return readValueUnderLock(e);// } e } } 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就会发生问题。
当然这种情况发生的概率很小。
4)遍历
其实这个过程和读取过程类似,读取所有分段中的数据即可。
ConcurrentHashMap默认情况下采用将数据分为16个段进行存储,并且每个段各自拥有自己的锁,锁仅用于put和remove等改变集合对象的操作,基于voliate及hashEntry链表的不变性实现读取的不加锁。
这些方式使得ConcurrentHashMap能够保持极好的并发操作,尤其是对于读远比插入和删除频繁的map而言,而它采用的这些方法也可谓是对于java内存模型、并发机制深刻掌握的体现,是一个设计得非常不错的支持高并发的集合对象。
——摘自《分布式java应用》
CopyOnWriteArrayList
CopyOnWriteArrayList是一个线程安全、并且在读操作时无锁的ArrayList。
1)添加
add(E e)
public boolean add(E final ReentrantLockthis.lock; lock.lock(); try { Object[] int len Object[]1);//复制数组 newElements[len]//添加到末尾 setArray(newElements); return true; }finally { lock.unlock(); } } |
这里同样没有使用synchronized关键字,而是使用ReentrantLock。
和ArrayList不同的是,这里每次都会创建一个新的object数组,大小比之前数组大1。将之前的数组复制到新数组,并将新加入的元素加到数组末尾。
2)删除
remove(Object o)
public boolean remove(Object final ReentrantLockthis.lock; lock.lock(); try { Object[] int len if (len0) // // int newlen1; Object[]new Object[newlen];//新建数组 for (int i0; if (eq(o, // for (int k1; newElements[k-1] setArray(newElements); return true; }else newElements[i] } // if (eq(o, setArray(newElements); return true; } } return false; }finally { lock.unlock(); } } |
此方法为什么这么直接进行数组的复制呢?为何不适用system的arrayCopy来完成?
3)获取
get(int index)
public Eint index) return (E)(getArray()[index]); } |
这里有可能脏读。但是销量非常高。
//通过看集合包和并发包可以看出一些不同的编程思路。这里为什么就不事先做范围的检查?
从上可见,CopyOnWriteArrayList基于ReentrantLock保证了增加元素和删除元素动作的互斥。在读操作上没有任何锁,这样就保证了读的性能,带来的副作用是有时候可能会读取到脏数据。
CopyOnWriteArraySet
CopyOnWriteArraySet是基于CopyOnWriteArrayList的,可以知道set是不容许重复数据的,因此add操作和CopyOnWriteArrayList有所区别,他是调用CopyOnWriteArrayList的addIfAbsent方法。
public boolean addIfAbsent(E final ReentrantLockthis.lock; lock.lock(); try { // // Object[] int len Object[]new Object[len1]; for (int i0; if (eq(e,//如果存在,直接返回! return false;// else newElements[i] } newElements[len] setArray(newElements); return true; }finally { lock.unlock(); } } |
由此可见,addIfAbsent需要每次都遍历,在add方面,CopyOnWriteArraySet效率要比CopyOnWriteArrayList低一点。
ArrayBlockingQueue
ArrayBlockingQueue是一个基于数组、先进先出、线程安全的集合类,其特点是实现指定时间的阻塞读写,并且容量是可以限制的。
1)创建
public ArrayBlockingQueue(int capacity,boolean fair) if (capacity0) throw new IllegalArgumentException(); this.itemsnew Object[capacity]; locknew ReentrantLock(fair); notEmpty notFull } |
初始化锁和两个锁上的Condition,一个为notEmpty,一个为notFull。
2)添加
offer(E e , long timeout , TimeUtil unit)
public boolean offer(Elong timeout, throws InterruptedException if (enull)throw new NullPointerException(); long nanos final ReentrantLockthis.lock; lock.lockInterruptibly(); try { for (;;) if (count insert(e); return true; } if (nanos0) return false; try { nanos }catch (InterruptedException notFull.signal();// throw ie; } } }finally { lock.unlock(); } } |
这个方法将元素插入数组的末尾,如果数组满,则进入等待,只到以下三种情况发生才继续:
被唤醒、达到指定的时间、当前线程被中断。
该方法首先将等待时间转换成纳秒。然后加锁,如果数组未满,则在末尾插入数据,如果数组已满,则调用notFull.awaitNanos进行等待。如果被唤醒或超时,重新判断是否满。如果线程被interrupt,则直接抛出异常。
另外一个不带时间的offer方法在数组满的情况下不进去等待,而是直接返回false。
public boolean offer(E if (enull)throw new NullPointerException(); final ReentrantLockthis.lock; lock.lock(); try { if (count return false; else { insert(e); return true; } }finally { lock.unlock(); } } |
同时还可以选择put方法,此方法在数组已满的情况下会一直等待,知道数组不为空或线程被interrupt。
public void put(Ethrows InterruptedException if (enull)throw new NullPointerException(); final E[]this.items; final ReentrantLockthis.lock; lock.lockInterruptibly(); try { try { while (count notFull.await(); }catch (InterruptedException notFull.signal();// throw ie; } insert(e); }finally { lock.unlock(); } } |
3)获取
poll(long timeout, TimeUnit unit)
public Elong timeout,throws InterruptedException long nanos final ReentrantLockthis.lock; lock.lockInterruptibly(); try { for (;;) if (count0) E return x; } if (nanos0) return null; try { nanos }catch (InterruptedException notEmpty.signal();// throw ie; } } }finally { lock.unlock(); } } |
poll获取队列中的第一个元素,如果队列中没有元素,则进入等待。
poll首先将制定timeout转换成纳秒,然后加锁,如果数组个数不为0,则从当前对象数组中获取最后一个元素,在获取后将位置上的元素置为null。
如果数组中的元素个数为0,首先判断timeout是否小于等于0,若小于0则直接返回null。若大于则进行等待,如果被唤醒或者超时,重新判断数据元素个数是否大于0。
如果线程被interrupt,则直接抛出InterruptedException。
和offer一样,不带时间的poll方法在数组元素个数为0直接返回null,不进行等待。
take方法在数据为空的情况下会一直等待,只到数组不为空或者interrupt。
java中并发包简要分析01的更多相关文章
- java中继承的内存分析
本文主要讲述java中继承的内存分析. 示例1,代码如下: public class EncapsulationTest { public static void main(String[] args ...
- [Java] Hashtable 源码简要分析
Hashtable /HashMap / LinkedHashMap 概述 * Hashtable比较早,是线程安全的哈希映射表.内部采用Entry[]数组,每个Entry均可作为链表的头,用来解决冲 ...
- Java中ArrayList源码分析
一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...
- Java中json工具对比分析
Java中几个json工具分析 1, 环境 JDK1.6+IDE(IntelliJ IDEA)+windowsXP+GBK编码 2,分析对象 jackson1.8.2 http://jackson.c ...
- Eclipse中的快捷键快速生成常用代码(例如无参、带参构造,set、get方法),以及Java中重要的内存分析(栈、堆、方法区、常量池)
(一)Eclipse中的快捷键: ctrl+shift+f自动整理选择的java代码 alt+/ 生成无参构造器或者提升信息 alt+shift+s+o 生成带参构造 ctrl+shift+o快速导 ...
- 大杂烩 -- Java中Iterator的fast-fail分析
基础大杂烩 -- 目录 Java中的Iterator非常方便地为所有的数据源提供了一个统一的数据读取(删除)的接口,但是新手通常在使用的时候容易报如下错误ConcurrentModificationE ...
- Java中Iterator的fast-fail分析
1.fail-fast简介 fail-fast机制是java集合(Collection)中的一个错误机制.当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件. 例如:当某一个线 ...
- Java中的<< 和 >> 和 >>> 分析理解
Java中的<< 和 >> 和 >>> 详细分析 <<表示左移移,不分正负数,低位补0: 注:以下数据类型默认为byte-8位 左移时不管正负,低 ...
- Java中I/O的分析
Java中I/O的原理: 在java程序中,对于数据的输入/输出操作以”流“的方式进行的. 流是内存中一组有序数据序列 Java将数据从源读入到内存当中,形成了流,然后这些流可以写到目的地. Java ...
- [Java] LinkedHashMap 源码简要分析
特点 * 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表.使用循环链表,保存了元素插入的顺序. * 可设置 ...
随机推荐
- Openharmony 跑 CV 应用
最近有个项目,老同学让帮忙验证一个在ARM 板上跑 OpenHarmony,然后再集成一个CV算法上去,写这个文章主要是整理一下思路.如果有思路不对的地方,也烦请指出. 1. 个人做纯软件比较多,所以 ...
- c++实现几种常见排序算法
一.快速排序 int getPivot(vector<int>& arr, int left, int right){ int tmp = arr[left]; while(lef ...
- C++:使自定义类支持迭代器
概述 在 C++ 中,链表迭代器是一种用来遍历链表(如 std::list)元素的工具.链表是一种数据结构,其中每个元素(节点)包含一个数据值和一个指向下一个节点的指针.链表迭代器允许以类似于数组的方 ...
- JavaScript – 冷知识 (新手)
当 charAt 遇上 Emoji 参考: stackoverflow – How to get first character of string? 我们经常会用 charAt(0) 来获取 fir ...
- 离线安装Redis
redis 直接去官网下载tar包就可以 主要是gcc 环境的安装包不太好找,我下载的还缺少 make 如果服务器比较干净,还得预装一下lrzsz-0.12.20.tar.gz 上传下载文件,unzi ...
- .Net 依赖注入深入探索,做一个DI拓展,实现一个简易灵活的 自动依赖注入框架
一.依赖注入相关知识 1.1.依赖注入的原理和优点 依赖注入(DI),是IOC控制反转思想 的实现.由一个DI容器,去统一管理所有的服务生命周期,服务的创建.销毁.获取,都是由DI容器去处理的. 依赖 ...
- 1.2 HELLO 三角形
这一节,我觉得是相当有难度的.渲染一个三角形,就需要介绍GLSL语言,图形渲染管线(Graphics Pipeline)以及着色器(Shader),标准化设备坐标(NDC)等诸多概念. 图形渲染管线和 ...
- redis - 认识 nosql 认识 redis 基础 linux安装 redis
sql和nosql的区别 1. 结构化 非结构化 2. 关联的 非关联的 3. sql查询 非 sql 4. 存储方式 磁盘 内存 5.扩展性 垂直 水平 6. 使用场景: 数据结构固定,相关业务 ...
- for循环、break和continue、二重循环
循环语句 循环语句可以反复多次执行同一组语句,for关键字可以用来编写循环:可以在for循环里让一个变量依次代表一组数字,然后使用同一组语句处理这个变量代表的每个数字.这个变量叫做循环变量,按照统一的 ...
- Oracle中查看隐含参数的sql
select a.ksppinm "Parameter", a.ksppdesc "Description", b.ksppstvl "Session ...