您的位置:首页 > 编程语言 > Java开发

Java 集合框架分析:线程安全的集合

2016-10-04 18:44 851 查看
相关文章:

Java 集合框架分析:Set

http://blog.csdn.net/youyou1543724847/article/details/52733723

Java 集合框架分析:LinkedList

http://blog.csdn.net/youyou1543724847/article/details/52734935

Java 集合框架分析:DelayQueue

http://blog.csdn.net/youyou1543724847/article/details/52176504

Java 集合框架分析:ArrayBlockingQueue

http://blog.csdn.net/youyou1543724847/article/details/52174308

Java 集合框架分析:ArrayDeque

http://blog.csdn.net/youyou1543724847/article/details/52170026

Java 集合框架分析:PriorityBlockingQueue

http://blog.csdn.net/youyou1543724847/article/details/52166985

Java 集合框架分析:JAVA Queue源码分析

http://blog.csdn.net/youyou1543724847/article/details/52164895

Java 集合框架分析:关于Set,Map集合中元素判等的方式

http://blog.csdn.net/youyou1543724847/article/details/52733766

Java 集合框架分析:ConcurrentModificationException

http://blog.csdn.net/youyou1543724847/article/details/52733780

Java 集合框架分析:线程安全的集合

http://blog.csdn.net/youyou1543724847/article/details/52734876

Java 集合框架分析:JAVA集合中的一些边边角角的知识

http://blog.csdn.net/youyou1543724847/article/details/52734918

说明:同步集合是说它的操作是同步的,(mutative operations :add, set, and so on),但是它的组合操作的同步性要自己控制。

线程安全的集合

什么是线程安全

在多线程环境下,不会产生不一致的行为(线程安全:有一定的标准,能够断定发生的先后顺序,如先获得锁的会执行,然后….)

分类

1.Copy*

CopyOnWriteArraySet,CopyOnWriteArrayList

2.Blocking*

ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, LinkedTransferQueue, PriorityBlockingQueue, SynchronousQueue

3.Concurent*

ConcurentHashMap, ConcurrentSkipListMap,ConcurrentSkipListSet

CopyOnWriteArrayList

实现:同步的方式是通过在需要修改集合时,通过copy底层的集合数据,在这之上操作。具体表现为:

1.The “snapshot” style iterator method uses a reference to the state of the array at the point that the iterator was created,迭代器在迭代器被创建时的集合快照上迭代,. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException。

2.The iterator will not reflect additions, removals, or changes to the list since the iterator was created. Element-changing operations on iterators themselves (remove, set, and add) are not supported. These methods throw UnsupportedOperationException。当迭代器创建后,集合上的任何变化都不会反映到迭代器上,通过迭代器进行修改集合会抛出不支持异常。

简单来说:迭代器在创建后就不变(所引用的集合元素是不变的),集合元素在迭代过程中是可变的。另外,因为迭代器在创建后所引用的集合元素不可变,即通过迭代器进行修改集合的操作都会抛出UnsupportedOperationException异常。



优缺点:

1.缺点:copy代价高

2.优点:迭代快

方法举例:

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();
}


ArrayBlockingQueue

同步通过可重入锁保证

/** Main lock guarding all access */
final ReentrantLock lock;

/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
private final Condition notFull;


方法举例

public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}


注意:Blocking*系列的提供有多种类似的方法,不同的方法的处理有点不一样。



这些方法的核心主体还是一样的,但是有的会根据具体的结果会进一步的处理,如:add方法,最终还是调用的offer方法,但是根据offer的返回值(插入成功还是不成功进一步的处理,看是否需要抛出异常)

public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}


其中offer和put的区别在offer是用lock.lock(),而put是用的lock.lockInterruptibly()方法进行加锁的,即put方法在等待锁时,你可以中断等待,使线程抛出InterruptedException 异常。(这两个的区别见:http://blog.csdn.net/youyou1543724847/article/details/52174968

public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}


ConcurentHashMap

这个最令人头疼了,看起源代码来。里面的同步主要是通过CAS操作来保证的。锁的粒度为桶级别,即同一个hashcode的链上的数据会被锁住。

>get操作没有加锁,是通过entry的volatile属性保证可见性。

public V get(Object key)
{
Node<K,V>[] tab;
Node<K,V> e, p;
int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null)
{
if ((eh = e.hash) == h)
{
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)// 树,或是 forwardnode等
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) // 链表
{
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}


final V putVal(K key, V value, boolean onlyIfAbsent)
{
if (key == null || value == null) throw new NullPointerException();

int hash = spread(key.hashCode());

int binCount = 0;
for (Node<K,V>[] tab = table;;)
{
Node<K,V> f;
int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();// table为空,初始化table
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null)
{//如果当前的bin是空的,则通过cas插入,不用加锁
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break;                   // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)//检测到正在扩容,则帮助其扩容
tab = helpTransfer(tab, f);
else
{
V oldVal = null;
synchronized (f) //对相应的bin进行上锁
{
if (tabAt(tab, i) == f) //检查锁的有效性,即之前锁住的对象还是bin的头结点,如果无效,则重试(重新走一边for循环)
{
if (fh >= 0) //当前的链还是list结构
{
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) //// 树节点
{
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}//插入节点
}
addCount(1L, binCount);
return null;
}


总结

在需要使用线程安全的集合时,选择适合的容器非常重要。可通过并发度、性能来考量

另外,有时候,当数据量较大时,可能这些容器其实已经不太适合,你需要通过别的方法来保证安全,如直接将数据放入到数据库中(或是内存NOSQL系统中)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程安全 Java集合