Java的并发容器和框架

1.线程安全的容器  --  ConcurrentHashMap

           背景:并发编程中使用HashMap线程不安全,容器导致程序死循环,而使用线程安全的hashtable则效率非常低下,基于上面的原因出现了ConcurrentHashMap。

  ConcurrentHashMap采用锁分段(Java7和Java8的源码有所不同,Java8底层用了红黑树)

static class Segment<K,V> extends ReentrantLock implements Serializable 

整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。

简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。每一个Segment里面相当于又是一个HashMap(数组加链表)

2.非阻塞算法的线程安全的队列 --  ConcurrentLinkedQueue 

         在并发编程中要实现线程安全的队列,方式有:

            1.使用阻塞的算法(使用锁实现,i>:  对于入队和出队使用同一把锁, ii> 或者入队一把锁,出队一把锁)

            2.使用非阻塞的算法(使用循环CAS实现)

          ConcurrrentLinkedQueue队列是属于非阻塞的方式实现线程安全,并且是属于无界线程安全队列

3.Java7中的阻塞队列

       3.1.概念:

             阻塞队列:阻塞队列是支持两个附加操作的队列,这两个附加的操作支持阻塞的插入和移出的方法

             1)支持阻塞的插入:意思是当队列满了的时候,队列会阻塞插入元素的线程,直到队列不满

             2)支持阻塞的移除:意思是当队列为空时,获取元素的线程会等待队列为非空

       3.2.这两个附加操作提供的处理方式方法

            

       1)抛出异常:当队列满时,如果在插入,会抛出IllegalStateException("Queue")异常,当队列空时,从队列为空时在获取会抛出NoSuchElementException异常。

       2)返回特殊值:当往队列里插入元素,会返回元素是否插入成功,成功返回true。如果是移除方法,则是从队列里取出一个元素,如果没有则返回null。

      3)一直阻塞:当阻塞队列满时,如果往里面put元素就会阻塞,知道队列可用或者响应中断退出。当队列空时,如果消费者线程往队列里take元素,队列就会阻塞消费者线程,知道队列不为空

      4)超时退出:当线程队列满时,往生产这线程中插入元素就会阻塞线程一段时间,如果超过了这个时间,线程就会退出

注意点:如果是无界阻塞队列,队列就不可能出现满的情况,所以使用put或offer方法就永远不会则色,而且使用offer方法永远返回true.

    3.3 Java里的阻塞队列

队列 有界性 数据结构
ArrayBlockingQueue 有界 加锁 arrayList
 LinkedBlockingQueue 有界 加锁 linkedList
PriorityBlockingQueue 无界 加锁 heap
DelayQueue 无界 加锁 heap
SyschronousQueue ---- 加锁 ----
LinkedTransferQueue 无界 加锁 linkedList
LinkedBlockingDeque 无界 加锁 linkedList

下面分别简单介绍一下:

  1. ArrayBlockingQueue:是一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序。支持公平锁和非公平锁。【注:每一个线程在获取锁的时候可能都会排队等待,如果在等待时间上,先获取锁的线程的请求一定先被满足,那么这个锁就是公平的。反之,这个锁就是不公平的。公平的获取锁,也就是当前等待时间最长的线程先获取锁】

  2. LinkedBlockingQueue:一个由链表结构组成的有界队列,此队列的长度为Integer.MAX_VALUE。此队列按照先进先出的顺序进行排序。
  3. PriorityBlockingQueue: 一个支持线程优先级排序的无界队列,默认自然序进行排序,也可以自定义实现compareTo()方法来指定元素排序规则,不能保证同优先级元素的顺序。
  4. DelayQueue: 一个实现PriorityBlockingQueue实现延迟获取的无界队列,队列中的元素必须实现delayed接口,在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。(DelayQueue可以运用在以下应用场景:1.缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。2.定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。)
  5. SynchronousQueue: 一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
  6. LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列,相当于其它队列,LinkedTransferQueue队列多了transfer和tryTransfer方法。
  7. LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半。

Java中线程安全的内置队列还有:ConcurrentLinkedQueue,它使用了CAS这种无锁的方式来实现了线程安全的队列。无锁的方式性能好,但是队列是无界的,用在生产系统中,生产者生产速度过快,可能导致内存溢出。有界的阻塞队列ArrayBlockingQueue和LinkedBlockingQueue,为了减少Java的垃圾回收对系统性能的影响,会尽量选择array/heap格式的数据结构。这样的话就只剩下ArrayBlockingQueue。

猜你喜欢

转载自blog.csdn.net/weixin_40792878/article/details/81516283
今日推荐