Java并发容器大合集
- JUC包中List接口的实现类:CopyOnWriteArrayList
    
- CopyOnWriteArrayList是线程安全的ArrayList
 
 - CopyOnWriteArrayList是线程安全的ArrayList
 - JUC包中Set接口的实现类:CopyOnWriteArraySet、ConcurrentSkipListSet
    
- CopyOnWriteArraySet是线程安全的Set,它内部包含了一个CopyOnWriteArrayList,因此本质上是由CopyOnWriteArrayList实现的。
 - ConcurrentSkipListSet相当于线程安全的TreeSet。它是有序的Set。它由ConcurrentSkipListMap实现。
 
 - CopyOnWriteArraySet是线程安全的Set,它内部包含了一个CopyOnWriteArrayList,因此本质上是由CopyOnWriteArrayList实现的。
 
- ConcurrentHashMap:线程安全的HashMap。采用分段锁实现高效并发。
 - ConcurrentSkipListMap:线程安全的有序Map。使用跳表实现高效并发。
 
- ConcurrentLinkedQueue:线程安全的无界队列。底层采用单链表。支持FIFO。
 - ConcurrentLinkedDeque:线程安全的无界双端队列。底层采用双向链表。支持FIFO和FILO。
 - ArrayBlockingQueue:数组实现的阻塞队列。
 - LinkedBlockingQueue:链表实现的阻塞队列。
 - LinkedBlockingDeque:双向链表实现的双端阻塞队列。
 
- PS:CopyOnWriteArraySet有CopyOnWriteArrayList实现。
 
- 适用于读操作远远多于写操作,并且数据量较小的情况。
 - 修改容器的代价是昂贵的,因此建议批量增加addAll、批量删除removeAll。
 
- 使用volatile修饰数组引用:确保数组引用的内存可见性。
 - 对容器修改操作进行同步:从而确保同一时刻只能有一条线程修改容器(因为修改容器都会产生一个新的容器,增加同步可避免同一时刻复制生成多个容器,从而无法保证数组数据的一致性)
 - 修改时复制容器:确保所有修改操作都作用在新数组上,原本的数组在创建过后就用不变化,从而其他线程可以放心地读。
 
// 添加集合中不存在的元素
int addAllAbsent(Collection<? extends E> c) 
  // 该元素若不存在则添加
boolean addIfAbsent(E e)
- CopyOnWriteArrayList拥有内部类:COWIterator,它是ListIterator的子类。
 - 当调用iterator函数时返回的是COWIterator对象。
 - COWIterator不允许修改容器,你若调用则会抛出UnsupportedOperationException。
 
- 数据一致性问题
    
- 由于迭代的是容器当前的快照,因此在迭代过程中容器发生的修改并不能实时被当前正在迭代的线程感知。
 
 - 内存占用问题
    
- 由于修改容器都会复制数组,从而当数组超大时修改容器效率很低。
 - PS:因此写时复制容器适合存储小容量数据。
 
 
- ConcurrentHashMap内部包含了Segment数组,而每个Segment又继承自ReentrantLock,因此它是一把可重入的锁。
 - Segment内部拥有一个HashEntry数组,它就是一张哈希表。HashEntry是单链表的一个节点,HashEntry数组存储单链表的表头节点。
 
V putIfAbsent(K key, V value)
- 它是一个有序的Map,相当于TreeMap。
 - TreeMap采用红黑树实现排序,而ConcurrentHashMap采用跳表实现有序。
 
- 它是一个有序的、线程安全的Set,相当于线程安全的TreeSet。
 - 它内部拥有ConcurrentSkipListMap实例,本质上就是一个ConcurrentSkipListMap,只不过仅使用了Map中的key。
 
- ArrayBlockingQueue是一个 数组实现的 线程安全的 有限 阻塞队列。
 
- ArrayBlockingQueue继承自AbstractQueue,并实现了BlockingQueue接口。
 - ArrayBlockingQueue内部由Object数组存储元素,构造时必须要指定队列容量。
 - ArrayBlockingQueue由ReentrantLock实现队列的互斥访问,并由notEmpty、notFull这两个Condition分别实现队空、队满的阻塞。
 - ReentrantLock分为公平锁和非公平锁,可以在构造ArrayBlockingQueue时指定。默认为非公平锁。
 
// 在队尾添加指定元素,若队已满则等待指定时间
boolean offer(E e, long timeout, TimeUnit unit)
// 获取并删除队首元素,若队为空则阻塞等待
E take()
// 添加指定元素,若队已满则一直等待
void put(E e) 
   // 获取队首元素,若队为空,则等待指定时间
E poll(long timeout, TimeUnit unit)
 
 - 队满阻塞:当添加元素时,若队满,则调用notFull.await()阻塞当前线程;当移除一个元素时调用notFull.signal()唤醒在notFull上等待的线程。
 - 队空阻塞:当删除元素时,若队为空,则调用notEmpty.await()阻塞当前线程;当队首添加元素时,调用notEmpty.signal()唤醒在notEmpty上等待的线程。
 
- LinkedBlockingQueue是一个 单链表实现的、线程安全的、无限 阻塞队列。
 
- LinkedBlockingQueue继承自AbstractQueue,实现了BlockingQueue接口。
 - LinkedBlockingQueue由单链表实现,因此是个无限队列。但为了方式无限膨胀,构造时可以加上容量加以限制。
 - LinkedBlockingQueue分别采用读取锁和插入锁控制读取/删除 和 插入过程的并发访问,并采用notEmpty和notFull两个Condition实现队满队空的阻塞与唤醒。
 
- 队满阻塞:若要插入元素,首先需要获取putLock;在此基础上,若此时队满,则调用notFull.await(),阻塞当前线程;当移除一个元素后调用notFull.signal()唤醒在notFull上等待的线程;最后,当插入操作完成后释放putLock。
 - 队空阻塞:若要删除/获取元素,首先要获取takeLock;在此基础上,若队为空,则调用notEmpty.await(),阻塞当前线程;当插入一个元素后调用notEmpty.signal()唤醒在notEmpty上等待的线程;最后,当删除操作完成后释放takeLock。
 
- 它是一个 由双向链表实现的、线程安全的、 双端 无限 阻塞队列。
 
- 它是一个由单链表实现的、线程安全的、无限 队列。
 
- 它仅仅继承了AbstractQueue,并未实现BlockingQueue接口,因此它不是阻塞队列,仅仅是个线程安全的普通队列。
 
- head、tail、next、item均使用volatile修饰,保证其内存可见性,并未使用锁,从而提高并发效率。
 - PS:它究竟是怎样在不使用锁的情况下实现线程安全的?
 
关注公众号
					低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 
							
								
								    上一篇
								    
								
								Java并发编程的艺术(十三)——锁优化
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/80289271 自旋锁 背景:互斥同步对性能最大的影响是阻塞,挂起和恢复线程都需要转入内核态中完成;并且通常情况下,共享数据的锁定状态只持续很短的一段时间,为了这很短的一段时间进行上下文切换并不值得。 原理:当一条线程需要请求一把已经被占用的锁时,并不会进入阻塞状态,而是继续持有CPU执行权等待一段时间,该过程称为『自旋』。 优点:由于自旋等待锁的过程线程并不会引起上下文切换,因此比较高效; 缺点:自旋等待过程线程一直占用CPU执行权但不处理任何任务,因此若该过程过长,那就会造成CPU资源的浪费。 自适应自旋:自适应自旋可以根据以往自旋等待时间的经验,计算出一个较为合理的本次自旋等待时间。 锁清除 编译器会清除一些使用了同步,但同步块中没有涉及共享数据的锁,从而减少多余的同步。 锁粗化 若有一系列操作,反复地对同一把锁进行上锁和解锁操作,编译器会扩大这部分代码的同步块的边界,从而只使用一次上锁和解锁操作。 轻量级锁 本质:使用CAS...
 - 
							
								
								    下一篇
								    
								
								Java中的接口和抽象类。
接上篇。 Java中的继承和多态 Animals(动物)类在现实生活中找不出这样的一个实例,没有一个叫动物的动物。他的子类,譬如像狗,鸟,是具体的一个实例,但是动物类只是一个抽象的概念。一个认知上的抽象。那这样的类就为抽象类。 那将Animals类 设置为抽象类,规定动物的基本属性,基本方法,抽象方法,那继承他的子类就必须实现父类的抽象方法,若不实现,那继承的这个类也必须设置为抽象类。 抽象类中可以有构造函数,属性的权限可以为private,public, protected.,含有抽象方法的类必须声明为抽象类。 抽象函数在抽象类中只是声明,具体实现交给子类。 eg 在上面的例子上继续改造,将Animals中的eat()方法设置为抽象函数。在子类中去实现它。 子类 接口 接口是一组规范,是一个标准。它不表示事务,接口是给类用的。 接口作用。 1:一种规范。 2:解耦合,降低耦合度。 接口的现实举例,比如 人要遵守法律。。人就是一个类,法律就是一个接口。 类可以实现多个接口,这就相当于人要遵守多个规章制度。 接口之间可以多继承,这就相当于所有的法律都是宪法之下的。 ...
 
相关文章
文章评论
共有0条评论来说两句吧...

			
				
				
				
				
				
				
				
微信收款码
支付宝收款码