Java并发容器和Java线程池

2022-09-17 12:39:11

Java并发容器和Java线程池

ConcurrentHashMap

ConcurrentHashMap是线程安全且高效的HashMap

为什么要用ConcurrentHashMap呢?

因为在并发编程中使用HashMap可能会导致程序死循环,而使用线程安全的HashTable效率又非常低下

  1. HashMap

    在多线程环境下,因为有并发状况,所以不能使用HashMap。HashMap在并发执行put操作时会引起死循环,这是因为多线程会导致HashMap的Entry链表形成环形数据结构

  2. HashTable使用synchonrized来保证线程安全,所以效率低下

  3. ConcurrentHashMap的锁分段技术可有效提高并发访问率

ConcurrentHashMap的结构

1.8中加入了红黑树,树查询的效率更高

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成

Segment是一种可重入锁,在ConcurrentHashMap里扮演锁的角色

HashEntry则用于存储键值对数据,是一种数组和链表结构

要对HashEntry数组的数据进行修改首先要获得对应的Segment锁

ConcurrentHashMap操作

ConcurrentHashMap的三种操作get,put和size

  1. get操作

    实现非常简单,先经过一次再散列,然后使用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素。

    高效之处时整个get操作不需要加锁,除非读到的值是空才会加锁重读。因为它的get方法将要使用的共享变量都定义成volatile类型,能够再线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值

  2. put操作

    在操作共享变量时必须加锁。put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤:第一步是判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置,然后将其放在HashEntry数组里

  3. size操作

    统计所有Segment里元素的大小后求和

Java中的阻塞队列

如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。

使用阻塞算法的队列可以用一个锁或两个锁实现

使用非阻塞算法的队列则可以使用循环CAS的方式来实现

什么是阻塞队列?

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

  1. 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满
  2. 支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变成非空

阻塞队列常用于生产者和消费者的场景,生产者存放元素,消费者获取元素

  • BlockingQueue阻塞队列
    • 阻塞队列是局由自动阻塞功能的队列,线程安全;take方法移除队头,若队列无数据则阻塞直到有数据;put方法插入元素,如果队列已满就无法继续插入则阻塞直到队列里有了空闲空间
    • ArrayBlockQueue
      • 有界可指定容量、可公平
      • Put源码加锁,可中断的上锁方法。没满才可以入队,否则一直await等待。
    • LinkedBlockingQueue
      • 无界容量为MAX_VALUE,内部结构Node
      • 使用了两把锁take锁和put锁互补干扰
    • PriorityBlockingQueue
      • 支持优先级,无界队列
    • SynchronousQueue
      • 直接传递的队列,容量0,效率高线程池的CacheExecutorPool使用其作为工作队列
    • DelayQueue
      • 无界队列,根据延迟时间排序

线程池

好处:

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性

实现原理

  1. 线程池判断核心线程池是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务,如果核心线程池里的线程都在执行任务,则进入到下一个流程
  2. 线程池判断工作队列是否已经满。如果队列没有满,则将新提交的任务存储在这个工作队列里,如果工作队列满,则进入下一个流程
  3. 线程池判断线程池的线程是否都处于工作状态,如果没有,则创建新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务

线程池的使用

线程池的创建

需要输入几个参数

  1. corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建了
  2. runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列,可以选择上面列出的阻塞队列
  3. maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数
  4. ThreadFactory:用于创建线程的工厂
  5. RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采用一种策略处理提交的新任务

向线程池提交任务

  1. execute():用于提交不需要返回值的任务,所以无法判断是否执行成功
  2. submit():用于提交需要返回值的任务

关闭线程池

使用shutdown或shutdownNow来关闭线程池。他们的原理是遍历线程池的工作线程,然后组个嗲用线程的interrupt方法来中断线程

线程池的监控

  • taskCount:线程池需要执行的任务数量
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount
  • largestPoolSize:线程池里曾经创建过的最大线程数量
  • getPoolSize:线程池的线程数量
  • getActiveCount:获取活动的线程数
  • 作者:Lehends_on_win
  • 原文链接:https://blog.csdn.net/weixin_51438023/article/details/120690485
    更新时间:2022-09-17 12:39:11