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。
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();
}
}
三:TimeUnit TimeUnit.SECONDS.sleep(10); 四:CopyOnWriteArrayList
是线程安全的List实现,其底层数据存储结构为数组(Object[] array),它在读操作远远多于写操作的场景下表现良好,这其中的原因在于其读操作(get(),indexOf(),isEmpty(),contains())不加任何锁,而写操作(set(),add(),remove())通过Arrays.copyOf()操作拷贝当前底层数据结构(array),在其上面做完增删改等操作,再将新的数组置为底层数据结构,同时为了避免并发增删改, CopyOnWriteList在这些写操作上通过一个ReetranLock进行并发控制。
线程安全的,一个读,一个写复制数据后,读写针对不同对象,当写完后,将对象引用重新赋值,保持最新