Java并发容器并发集合

  BlockingQueue接口定义了一种阻塞的FIFO queue,每一个BlockingQueue都有一个容量,让容量满时往BlockingQueue中添加数据时会造成阻塞,当容量为空时取元素操作会阻塞。
  适用阻塞队列的好处:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距。
  
一、 ArrayBlockingQueue
  是一个由数组支持的有界阻塞队列。在读写操作上都需要锁住整个容器,因此吞吐量与一般的实现是相似的,它使用一把全局的锁并行对queue的读写操作(性能相对较低),同时使用两个Condition(count == items.length/0)阻塞容量为空时的取操作和容量满时的写操作。如果知道队列的大小,那么使用ArrayBlockIngQueue就比较合适了,因为它使用循环数组实现。
  
二、 LinkedBlockingQueue
  基于链表的阻塞队列,当生产者往 队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列尾部;只有当队列缓冲区达到最大值缓存容量时,才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别 采用了独立的锁来控制数据同步(它对头和尾(取和添加操作)采用两把不同的锁)(LinkedBlockingQueue内部使用ReentrantLock实现插入锁 (putLock)和取出锁(takeLock)。putLock上的条件变量是notFull,即可以用notFull唤醒阻塞在putLock上的线程。takeLock上的条件变量是notEmtpy,即可用notEmpty唤醒阻塞在takeLock上的线程。),这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能,提高了吞吐量,适合于实现“消费者生产者”模式。但是在大多数并发应用程序中,其可预知的性能要低。(不接受null元素)
  
ArrayBlockingQueue和LinkedBlockingQueue的区别:
1. 队列中锁的实现不同
ArrayBlockingQueue实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;
LinkedBlockingQueue实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock
2. 在生产或消费时操作不同
ArrayBlockingQueue实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;不会产生或销毁任何额外的对象实例;
LinkedBlockingQueue实现的队列中在生产和消费的时候,需要把枚举对象转换为Node进行插入或移除,会生成一个额外的Node对象,这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。
3. 队列大小初始化方式不同
ArrayBlockingQueue实现的队列中必须指定队列的大小;
LinkedBlockingQueue实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE, 当然也可以指定队列大小,从而成为有界的。(在使用LinkedBlockingQueue时,若用默认大小且当生产速度大于消费速度时候,有可能会内存溢出。)
4. ArrayBlockingQueue take操作并没有元素的移动
采用的是 Circularly increment (return(++i==items.length)?0:i;)
ArrayBlockingQueue的速度是优于LinkedBlocingQeque,他们主要的区别是在bound上 。

  JDK1.5之后,允许在集合迭代时修改集合,迭代器被设计成不是fail-fast
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

三、CopyOnWriteArrayList(Set)写时复制
  CopyOnWriteArrayList类是一个线程安全的List接口的实现,在该类的内部进行元素的写操作时,底层的数组将被完整的复制,(ArrayList同步锁开销大,但是这个不用锁定,保留线程安全而不用锁定)适合于遍历操作多,多读少写。在CopyOnWriteArrayList上进行操作时,读操作不需要加锁,而写操作类实现中对其进行了加锁。适合于遍历操作多。
  CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制 出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发 的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
缺点
1.内存占用问题,因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。
2.数据一致性,只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

四、ConcurrentHashMap
  Jdk1.0 HashTable | Jdk1.2 HashMap 使用Collections.synchronizedMap 不同步的基类有一个同步的包装器。
  同步缺点:1.可伸缩性差,一次只有一个线程操作hash表;2.不是真正的线程安全,混合操作需额外同步。
  Map的一种并发实现。在该类中元素的read操作都是无锁了,而write操作需要被同步。这非常适合于读操作远大于写操作的情况。在实现过程 中,ConcurrentHashMap不是对一个表一个锁,而是将一个表中所有元素分成了若干个segment,每个segment是独立的,在一个segment上加锁并不影响其他 segment的操作(多个线程访问map不同部分,插入,检索好,但是size等困难,需要一次获得多个锁)。segment本身是一个hashtable,对于一个加入ConcurrentHashMap的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值