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

说明:同步集合是说它的操作是同步的,(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异常。

0818b9ca8b590ca3270a3433284dd417.png

优缺点:

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*系列的提供有多种类似的方法,不同的方法的处理有点不一样。

0818b9ca8b590ca3270a3433284dd417.png

这些方法的核心主体还是一样的,但是有的会根据具体的结果会进一步的处理,如: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://www.voidcn.com/article/p-paixruoy-pb.html)

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[] tab;

Node 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[] tab = table;;)

{

Node 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(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 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 pred = e;

if ((e = e.next) == null) {

pred.next = new Node(hash, key,

value, null);

break;

}

}

}

else if (f instanceof TreeBin) 树节点

{

Node p;

binCount = 2;

if ((p = ((TreeBin)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系统中)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值