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

Java线程集合类ConcurrentHashMap&CopyOnWriteArrayList 深入

2014-02-17 20:28 519 查看
一:(阻塞队列)BlockingQueue(子类:常用LinkedBlockingQueue和ArrayBlockingQueue)
private BlockingQueue<MapMonitorEntry> entryQueue = new LinkedBlockingQueue<MapMonitorEntry>();
put( ) take( ) --阻塞方法,put当队列数据满会阻塞,take当队列空阻塞等待数据
offer 添加一个元素并返回true 如果队列已满,则返回false

poll 移除并返问队列头部的元素 如果队列为空,则返回null,TimeUnits等待时间后返回

peek 返回队列头部的元素 如果队列为空,则返回null

put 添加一个元素 如果队列满,则阻塞

take 移除并返回队列头部的元素 如果队列为空,则阻塞
1)LinkedBlockingQueue:无界,生产者消费者是独立锁进行控制真正并行,插入或删除产生额外Node对象占内存堆
2)ArrayBlockingQueue:有界,共用锁并非真正并行,插入或删除不产生额外对象

tip:为什么需要rehash?

随着key不断的添加,bucket下的单链表越来越长,查找、删除效率越来越低,需要对ht进行expand,增加bucket(数组长度)个数,让链表的长度减少。bucket数量的增多,原有bucket的key需要迁移到新的bucket上,于是有了rehash的这个过程.

二:(并发容器类)ConcurrentHashMap

更具细粒度的锁,更高效,在segment上引入锁概念,32个段。HashTable是集合锁。但是 size() 和 containValues 方法是锁定整个表,而不是段。
细粒度锁,分离锁,较HashMap,高并发情景下(50-100线程并发)添加和删除性能提高一倍,查找性能提高10倍
(HashTable:锁整个hash表,ConcurrentHashMap废除对整个hash表锁操作,转而在每个bucket加锁,锁粒度细化)
ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。(ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的 final修饰,value是volatile保证看到最新)。

get:读不需要锁volatile V value(保证立可见)
size:一个Segment中的有一个modCount变量,代表的是对Segment中元素的数量造成影响的操作的次数,这个值只增不减,size操作就是遍历了两次Segment,每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同,则把这个过程再重复做一次,如果再不相同,则就需要将所有的Segment都锁住,然后一个一个遍历。
Segment:每个段继承ReentrantReadWriteLock锁,每个段自带读写锁remove,put操作
count:每个put,remove操作都会更新count值,所以易变的volatile transient volatile int count;
modcount:只增不减值,只是为比较集合是否有变动,如果变动所有segment加锁,否则直接取值求和
迭代写:遍历同时写操作不会发生ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
remove:HashEntry中的next是final的,一经赋值以后就不可修改,在定位到待删除元素的位置以后,程序就将待删除元素前面的那一些元素全部复制一遍,然后再一个一个重新删除前:删除后:
containsValue
ConcurrentHashMap.containsValue(Object value):
for (int i = 0; i < segments.length; ++i) {
int c = segments[i].count;
mcsum += mc[i] = segments[i].modCount;
if (segments[i].containsValue(value))//遍历所有段,每段都要锁
return true;
}

ConcurrentHashMap$.containsValue(Object value):

boolean containsValue(Object value) {
readLock().lock();//读锁
try {
if (count != 0) { // read-volatile
HashEntry<K,V>[] tab = table;
int len = tab.length;
for (int i = 0 ; i < len; i++) {
for (HashEntry<K,V> e = tab[i]; e != null; e = e.next)
{
V v = e.value;
if (value.equals(v))
return true;
}
}
}
return false;
} finally {
readLock().unlock();
}
}
static final class HashEntry<K,V> {

final K key;

final int hash;

volatile V value;

final HashEntry<K,V> next;//后续节点删除,不能重新赋值链接,之前节点重新复制一遍

}

三:synchronizedMap 和 synchronizedList(有条件线程安全)
单个操作是线程安全的,相当于对原集合每个操作方法加集合的对象锁,但是多个操纵组成的操纵序列却可能导致数据争用,由于在操纵序列中控制流取决于前面操纵的结果
多个操作size put get组合外面必须加该集合对象锁,容易发生ConcurrentModifyException
四:CopyOnWriteArrayList 与 CopyOnWriteArraySet
(“写入时复制”策略)基本思想是一旦对容器有修改,那么就“复制”一份新的集合,在新的集合上修改,然后将新集合复制给旧的引用。当然了这部分少不了要加锁。读取不需要加锁
volatile

List仍然是基于数组的实现,因为只有数组是最快的。
为了保证无锁的读操作能够看到写操作的变化,因此数组array是volatile类型的。
get/indexOf/iterator等操作都是无锁的加volatile即可,同时也可以看到所操作的都是某一时刻array的镜像(这得益于数组是不可变化的)
add/set/remove/clear等元素变化的都是需要加锁的,这里使用的是ReentrantLock。

private volatile transient Object[] array;//volatile易变量修改后其它线程立即可见,transient非序列化(非持久字段)
public E set(int index, E element) {

final ReentrantLock lock = this.lock;

lock.lock();

try {

Object[] elements = getArray();

Object oldValue = elements[index];

if (oldValue != element) {

int len = elements.length;

Object[] newElements = Arrays.copyOf(elements, len);

newElements[index] = element;

setArray(newElements);

} else {

// Not quite a no-op; ensures volatile write semantics

setArray(elements);

}

return (E)oldValue;

} finally {

lock.unlock();

}

}
ConcurrentModifyException

在遍历collection和set时,不要remove其中的元素。
如果remove的话,先iterate一下,再使用iterrate的remove()方法。
如下所示:
for(Iterate it=Correction.iterate();it.hasNext();)
{
XX=it.next();
if(......)
{
it.remove();
}
}
或者

Collection<User> users =new CopyOnWriteArrayList();//如果new ArrayList() 会 CollectionModifyException
users.add(new User("张三",28));
users.add(new User("李四",25));
users.add(new User("王五",31));
for(User user:users){
users.remove(user);
}
System.out.println(users.size());

三:TimeUnit
TimeUnit.SECONDS.sleep(10);
四:CopyOnWriteArrayList

是线程安全的List实现,其底层数据存储结构为数组(Object[] array),它在读操作远远多于写操作的场景下表现良好,这其中的原因在于其读操作(get(),indexOf(),isEmpty(),contains())不加任何锁,而写操作(set(),add(),remove())通过Arrays.copyOf()操作拷贝当前底层数据结构(array),在其上面做完增删改等操作,再将新的数组置为底层数据结构,同时为了避免并发增删改, CopyOnWriteList在这些写操作上通过一个ReetranLock进行并发控制。
线程安全的,一个读,一个写复制数据后,读写针对不同对象,当写完后,将对象引用重新赋值,保持最新
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: