Java并发编程-并发工具

文章目录

目录

文章目录

前言

一、线程池

二、JUC(Java-Util-Concurrent)

三、线程安全集合类

总结


前言

主要介绍了Java并发编程的并发工具。


一、线程池

  • 创造线程池的原因
    • 为每一个任务分配一个线程会造成
      • 栈太多了导致OFM
      • 每次一个任务执行完会导致频繁的上下文切换
    • 原理
      • 创造线程池,使任务轮流被固定数量的线程池中线程执行
  • 状态+线程数量
    • 前三位记录状态,后面位记录线程数量,写在一个整数标记里面可以减少一次CAS
    • 五状态
      • RUNNING
        • 正常运行状态
      • SHUTDOWN
        • 不接受新任务,已经提交的阻塞队列中剩余任务会处理完
      • STOP
        • 不接受新任务,已经提交的阻塞队列中剩余任务也不会执行
      • TIDYING
        • 任务执行完毕,活动线程为0时即将进入终结
      • TERMINATED
        • 终结状态
  • 构造方法
    • 拯救线程
      • 有界队列中,最大线程数 - 核心线程数=拯救线程数
      • 核心线程满 & 任务队列满时,新任务进入拯救线程执行
    • 拒绝策略
      • 核心线程满 & 任务队列满 & 拯救线程满时,拒绝策略作为线程构造函数的参数传入(不在构造函数中固定写好,可以由编程人员动态确定)
      • 各种拒绝策略的实现
        • JDK
          • AbortPolicy
            • 默认,抛出异常
          • CallerRunsPolicy
            • 调用者完成任务
          • DiscardPolicy
            • 放弃当前任务
          • DiscardOldestPolicy
            • 放弃任务中最早的任务,当前任务取而代之
        • Duboo
          • 抛出异常前记录日志
        • Netty
          • 创建新线程执行
        • ActiveMQ
          • 超时等待放入任务队列
        • PinPoint
          • 逐一实现拒绝策略链中的各种拒绝策略
  • 类型
    • 固定大小newFixedThreadPool
      • 特点
        • 没有拯救线程(固定数量核心线程),没有超时时间
        • 无界阻塞队列,任意数量任务
      • 适用场景
        • 线程数固定,相对耗时的任务
    • 缓存池newCachedThreadPool
      • 特点
        • 每个任务都由拯救线程完成(60s后可回收),拯救线程无限创建
        • 必须有线程来取,才能把任务放进去
      • 使用场景
        • 线程数多,执行时间短
    • 单线程newSingleThreadExcutor
      • 区别
        • 普通线程new Thread
          • 任务失败,普通线程没有任何补救措施
          • 单线程池会新建线程,保证池正常完成任务
        • 固定大小=1线程池newFixedThread(1)
          • 固定大小线程池返回的是ThreadPoolExecutor对象,强转类型后可以调用setCorePoolSize方法修改池中线程数
          • 单线程池返回的是装饰器模式保护的ExecutorService接口,不能调用ThreadPoolExecutor中特有方法,池中永远只有一个线程
      • 适用场景
        • 多任务排队执行(仅有一个任务在被线程执行,其余任务在无界队列中排队),任务执行完成后唯一线程不会被释放
  • 提交任务
    • Future<T> submit(Callable<T> task)
      • 提交一个任务,用返回值Future等待任务执行结果(调用其中的get方法)
    • List<Future<T>> invokeAll(Collection<?extends Callable<T>> tasks)
      • 可以带超时时间
      • 提交所有任务,将每个任务的执行结果存入队列返回
    • T invokeAny(Collection<? extends Callable<T>> tasks)
      • 可以带超时时间
      • 提交所有任务,返回最先执行完成的任务结果,其他任务取消
  • 关闭线程池
    • void shutdown()
      • 线程状态变为SHUTDOWN,不接受新任务,已经提交的任务会执行完
      • 不会阻塞调用线程的执行(线程调用完shutdown后,本身的执行不会受影响)
    • List<Runnable> shutdownNow()
      • 线程状态变为STOP,不接受新任务,已经提交的任务会interrupt中断
      • 等待队列中的任务会被返回

二、JUC(Java-Util-Concurrent)

  • AQS抽象队列同步器
    实现Java层面各类锁的底层同步器(C++是synchronize的Monitor)
    • AbstractQueueSynchronizer,是阻塞式锁和相关的同步器工具的框架,ReentrantLock继承该同步器然后实现其方法
    • 底层调用park、unpark方法阻塞线程等待锁,结合cas修改锁状态state
    • 内部属性
      • state属性表示资源状态(独占模式 / 共享模式)
        • 子类实现抽象同步器中的方法(tryAcquire/tryRelease),定义如何维护state状态,控制获取锁和释放锁
        • get/set state、compareAndSetState
        • 独占模式只允许一个线程访问资源,共享模式允许多个线程访问资源
      • 等待队列
        • 相当于Monitor的EntryList,Head + Tail组成双向队列,第一个节点Node是Dummp哑元/哨兵
      • 条件变量
        • 支持多条件变量Condition,相当于Monitor的WaitSet,firstWaiter + lastWaiter组成双向队列,没有哨兵节点
  • ReentrantLock原理
    独占锁,等待队列实现多线程互斥获得锁,条件变量队列实现多线程同步协作
    • 继承同步器AQS,实现其内部方法,底层调用park、unpark和cas实现互斥独占锁
    • 加锁lock
      • 成功
        • tryAcquire执行cas设置state=1,设置Owner线程
      • 失败
        • state=1状态拒绝加锁,循环多次tryAcquire失败后,supportLock调用park,将该线程放入head连接的哨兵节点(状态为-1,可以unpark打断第一个节点对应的等待线程)后的节点中(最后一个状态为0,非最后一个状态为-1,可以unpark打断下一个节点对应的等待线程),进入等待锁的队列
    • 解锁unlock
      • 竞争成功
        • tryRelease执行cas设置state=0,清空Owner线程,没有新线程竞争锁,head连接的哨兵节点unpark解锁唤醒第二个节点存放的线程,tryAcquire成功获得锁,cas设置state=1,重设Owner线程,断开初始哨兵,将原本存放自己的节点变为哨兵(Thread=null,状态=-1(后面还有节点,无=0))
      • 竞争失败
        • tryRelease执行cas设置state=0,清空Owner线程,head连接的哨兵节点unpark解锁唤醒第二个节点存放的线程竞争锁时,有新线程也竞争锁,被唤醒线程tryAcquire失败未获得锁再次park阻塞等待,新线程成功tryAcquire获得锁,cas设置state=1,重设Owner线程,其余不做改变
    • 锁重入
      • lock线程tryAcquire获得自己已经获得的锁时state++,unlock线程TryRelease释放锁时state--到state=0时才能成功释放锁
    • 打断
      • 不可打断
        • 陷入park(不清除打断标记)时,被interrupt后,通过interrupted获得打断标记并清除,线程不结束执行,获得锁后只是知道打断标记为真,自己打断自己
      • 可打断
        • 线程陷入park时,被interrupt时,直接报出异常,停止运行
    • 公平锁
      • 在tryAcquire时,需要确保head连接的哨兵节点之后没有连接等待锁的线程,才去尝试获得锁,若有,则不竞争锁,将锁让给哨兵节点后的第二个之前就请求等待锁的节点存的线程
      • 非公平锁,无论head连接的哨兵节点后是否有第二个节点存放之前就等待的线程,新来的线程都直接tryAcquire竞争线程
    • 条件变量
      • await
        • 获得锁的Owner线程中条件变量调用await时,该线程将自己放入调用者Condition的firstWaiter连接的第一个Node节点中(没有哨兵节点),tryRelease解锁执行cas设置state,清空Owner线程,等待队列中head连接的哨兵节点会unpark唤醒其连接的线程开始竞争锁
      • signal
        • 从条件变量等待队列中取出firstWaiter连接的第一个节点,加入锁等待队列的最后一位(tail前一位),将其状态设置为0,其前一位(本来状态为0)状态变为-1;若加入锁等待队列失败,会重新尝试获得条件变量等待队列中下一个等待节点Node,并将其放入锁等待队列队尾(signalAll会唤醒当前条件变量中所有等待线程)
  • reentrantreadwriteLock读写锁
    读锁、写锁分开,实现读读并发共享不互斥
    • 用途
      • 对读和写上不同的锁,使得读读操作可并发共享锁执行(读写、写写操作还是要互斥独占锁),因为读操作发生的次数远多于写操作,读写锁可以提高效率
    • 注意点
      • 读写锁发生锁重入不能锁升级(读->写),只能锁降级(写->读)
      • 应用案例
        • 上了读锁必须先解读锁再上写锁,解锁之前必须先锁降级,(已经上了写锁的情况下)上读锁后再释放写锁,防止直接释放写锁后没有锁保证读写操作互斥,最后再释放读锁
  • semaphore信号量
    信号量多线程共享锁
    • 允许一个共享资源被多线程同时访问的共享模式锁
    • 原理
      • 继承同步器AQS,允许访问共享资源的最多线程数作为构造方法的permit许可参数传入,cas设置state=permit
      • 上锁
        • 线程tryAcquire尝试获取Semaphore,cas设置state--,只要state>0可以一直上锁成功;当state=0时,线程获得锁失败,park线程,将线程放入head连接哨兵节点(状态=-1,可以唤醒后一个阻塞等待的线程)后的节点中,阻塞等待锁释放
      • 释放锁
        • 线程tryRelease释放Semaphore,cas设置state++,state>0让哨兵节点unpark唤醒哨兵节点后连接的节点中的等待锁的线程,该线程tryAcquire获得锁,成功cas设置state--,获得锁开始执行,断开初始哨兵,将原本存放自己的节点变为哨兵,只要state>0可以重复让线程尝试获取锁;失败时(锁被别的线程抢到,state=0)tryAcquire失败,再次park等待(被唤醒一次后又再次陷入阻塞)
  • countdownlatch倒计时锁
    共享锁多线程同步协作,实现互相等待
    • 功能
      • 倒计时锁,相当于线程的join方法(join是底层方法),让线程等待其他线程执行完成后再继续执行,实现多个线程之间的同步协作执行
      • 相比于join方法,countDownLatch适用于更多场景;join是让某线程等待其他线程执行完成以后再继续执行,但是线程池中的线程永远在等待接受任务,永远在执行不会结束,若用join方法则无法实现线程池中多任务的同步协作功能,线程池与countDownLatch组合更合理
      • 在线程无需获取其他线程返回值的情况下,适用于countDownLatch,若需要等待其他线程返回值,可以用Future中的get方法等待其他线程返回值
    • 用法
      • 将倒计时数值作为构造函数的参数传入(并且cas设置为state),需要等待的线程用countDownLatch调用awaite方法(内部调用tryAcquire)等待倒计时数值state减为0,在同步协作线程中用countDownLatch调用countDown方法(内部调用tryRelease)对倒计时数值state-1
    • 原理
      • 同步器继承AQS,cas设置state
      • tryAcquire尝试获得共享锁时,若state=0则获得锁成功,若state>0则park阻塞等待其他线程将倒计时数值减为0
      • tryRelease释放共享锁时,state-1,若-1结束后state=0则返回真unpark唤醒阻塞线程,state>0则返回假不唤醒阻塞线程
  • Future
    多线程同步协作,实现互相等待返回值
    • 应用情况
      • 多线程同步协作情况下,线程需要等待其他线程的返回结果
    • 示例
      • 线程池中submit提交任务有Future类型返回值,在主线程中用Future调用get方法等待任务线程返回值,实现多线程同步协作执行
  • cyclicbarrier循环栅栏
    可重用换代的实现多线程同步协作,”线程齐了再出发“
    • 用途
      • countDownLatch(共享锁)的升级(cyclicbarrier独占锁),countDownLatch计数减为0后,只有重新创建一个新的countDownLatch对象再次传入计数,用于实现多线程同步协作
      • cyclicbarrier构造函数参数1传入parties参数作为同步线程计数量,每当线程数到达线程同步计数量时,成功完成一次多线程同步协作,然后初始计数值归0换代;再次调用时,初始计数值随线程调用增加,再次到达传入的线程同步计数量时,会再次成功执行一次多线程同步协作(无需重新创建cyclicbarrier对象,可反复使用于实现多线程同步协作)
      • cyclicbarrier构造函数参数2传入线程到齐后,所有线程恢复执行完成后,待做的收尾任务
    • 实际过程
      • 线程需要同步的时刻,cyclicbarrier会调用await方法,线程计数加1,若计数未达到传入的parties参数线程同步计数量,会阻塞等待计数增加到要求时再被唤醒(=等待线程到齐),所有线程一起恢复执行完成后,执行cyclicbarrier构造函数的参数2传入的收尾任务
    • 注意
      • 往构造函数中传入的同步计数参数1 parties=开始同步协作的必要线程数

三、线程安全集合类

  • 概述
    • 遗留的安全集合
      • Hashtable、Vector
    • Connection.synchronized修饰的安全集合
      • Connection.synchronizedList、Connection.synchronizedSet、Connection.synchronizedMap
    • J.U.C安全集合
      • Blockling
        • 大部分实现基于锁用来阻塞的方法
      • CopyOnWrite
        • 写操作开销大于读操作,用于读操作大程度多过写操作的情况,弱一致性
      • Concurrent
        • 优点
          • 用 cas(不上锁,共享变量被改变则重试获取最新值) + 多把细密度锁(需要不同锁的线程可以并发执行) 实现优化,提供高吞吐量和高并发的性能
        • 缺点
          • 导致弱一致性,遍历(迭代器遍历时,线程修改共享变量,可能读到旧值)、求大小(size操作可能得到错误值)、读取(可能读的过程中被其他线程修改读到旧值)
          • 遍历弱一致性
            • 非安全集合
              • fail-fast,遍历时发生修改立即停止遍历,报出异常
            • 安全集合
              • fail-safe,遍历时发生修改不立即停止遍历,可能遍历得到错误值
  • concurrenthashmap并发hashmap
    • 安全集合的方法单独使用时线程安全,多个安全集合的方法组合使用时线程不安全
    • computerIfAbsent+AtomicAdder
      • value compuerIfAbsent(key,value);如果key不存在则计算value,并且返回value
      • computerIfAbsent方法可以代替线程不安全的get和put方法组合,安全的实现其目标功能,同时用原子累加器计算value保证value值计算的安全
    • JDK7并发使用非安全集合hashMap产生死链
      • 多线程并发时,使用了线程不安全的hashmap集合
      • JDK7版本下,线程a与线程b同时并发使用hashMap扩容,线程a完成扩容产生了新链表,线程b还在扩容中,由于线程a扩容成功导致其链表内容被改变,线程b扩容时取到新链表中错误顺序的数据导致死链
      • 并发时会拿到扩容新表中错误顺序的数据,底层原因是,JDK7版本中hashmap的新元素会插入链表的头部,扩容时从旧链表中获取到最新数据,插入在新表的头处,再加入新数据后该数据会被压到链表尾,导致扩容前后元素顺序不同(扩容前新数据在头,扩容后新数据在尾)
      • JDK8调整了扩容算法,元素不在加入链表头,而是保证扩容前后元素顺序相同(不扩容新加在链表头,扩容时新加在链表尾),但是这种情况会出现更频发的扩容丢数据问题,多线程并发用非安全集合hashmap扩容还是不安全
    • concurrenthashMap原理
      • JDK8
        • cas+细密度锁加在链表头(随着扩容锁也变多并发度也提高 & 懒惰第一次使用时才创建)提高并发度;初始化大小会自动传化为2^n次方而非自己设置的值;forwardingNode在扩容时标记已经处理过的链表头,在扩容时读到fwn则在新链表中读数据;链表节点数达到阈值8时,查找效率降低,先扩容,扩容至链表头数大于64时,将链表升级为黑红树
      • JDK7
        • cas+细密度锁加在segment(分段锁继承自ReentrantLock本身就是一把锁)提高并发度;与JDK8相比,segment数组大小固定不能随着扩容提高并发度、segment数组和segment[0]非懒惰创建(使用时才初始化),一开始就固定大小的创建;shift移位+Mark掩码计算key对应的segment值
  • linkedblockingqueue阻塞链表队列
    • 实现细节
      • 两把锁 + 哨兵节点 + 条件变量,懒惰(第一次用时)初始化,入队创建Node,支持有界,实现多线程高并发的阻塞、同步入队出队操作
      • 生产者入队时获得tail锁,await阻塞等待notFull条件变量,每次只signal一个线程防止提高竞争,可以自己唤醒自己
      • 消费者出队时获得head锁,await阻塞等待notEmpty条件变量,每次只signal一个线程防止提高竞争,可以自己唤醒自己
      • 两把锁使得生产者与消费者可以并发执行,提高效率
      • 哨兵Dummy节点可以在只有一个元素节点时保证head锁(Dummy)与tail锁(元素Node)锁住两个不同节点,防止生产者消费者拿到同一个锁;没有元素Node只有Dummy时,消费者会阻塞等待notEmpty条件变量
    • 与Arrayblockingqueue阻塞数组队列区别
      • 一把锁,Array强制有界、非懒惰(提前初始化)、提前创建好Node
  • concurrentlinkedqueue同步链表队列
    • 两把[锁]+ 哨兵节点 + 条件变量,实现生产者消费者同时执行,生产者[锁]Dummy,消费者[锁]元素Node或者阻塞等待notEmpty条件变量
    • [锁]用cas与volatile组合实现,不真正获得锁,允许线程修改,有线程修改时,重试获取最新值
  • copyonwritearraylist写入式拷贝数组列表
    • 增删改查操作时,作用于复制的新底层数组,原始数组不动,实现读读并发、读写分离并发(写写互斥)
    • 读不上锁,写复制上锁开销大,弱一致性(会读到旧数据)
    • 并发与一致性互斥,提高并发则会出现弱一致性问题,强一致性会上锁降低并发度


总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值