0%

Java 并发总结(二):线程的协作机制

1. 多线程的协作场景有哪些

  • 生产者/消费者模式
  • 主从协作模式
  • 同时开始
  • 集合点等待

2. 线程的协作机制有哪些

  • wait()/notify()

    • wait()/notify()synchronized 配合一起使用,是线程的基本协作机制
    • 每个对象都有一把锁两个等待队列,一个是锁等待队列,里面放的是等待获取锁的线程;另一个是条件等待队列,里面放的是等待条件的线程:wait() 的作用是将自己加入条件等待队列,notify() 的作用是从条件等待队列上移除一个线程并唤醒,notifyAll() 移除所有线程并唤醒
    • wait()/notify() 方法只能在 synchronized 代码块内被调用。调用 wait() 时,线程会释放对象锁,被 notify()/notifyAll() 唤醒后,要重新竞争对象锁,获取到锁后才会从 wait() 调用中返回,返回后,不代表其等待的条件就一定成立了,需要重新检查其等待的条件
    • wait()/nofity() 方法看上去简单,但往往难以理解 wait() 等的到底是什么,而 notify() 通知的又是什么,只能有一个条件等待队列,这也是 wait()/notify() 机制的局限性,这使得对于等待条件的分析变得复杂
  • 显示条件

    • 显示条件与显示锁配合使用,与 wait()/notify() 相比,可以支持多个条件队列,代码更为易读、效率更高
    • 使用时注意不要将 signal()/signalAll() 误写为 notify()/notifyAll()
  • 线程的中断

    • Java 中取消/关闭一个线程的方式是中断。中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,由线程来决定如何以及何时退出
    • 线程在不同状态和 IO 操作时对中断有不同的反应。作为线程的实现者,应该提供明确的取消/关闭方法,并用文档清楚描述其行为;作为线程的调用者,应该使用其取消/关闭方法,而不应该贸然调用 interrupt() 方法
  • 协作工具类

    • 信号量类 Semaphore 用于限制对资源的并发访问数
    • 倒计时门栓 CountDownLatch 主要用于不同角色线程间的同步。比如在裁判/运动员模式中,裁判线程让多个运动员线程同时开始,也可以用于协调主从线程,让主线程等待多个线程的结果
    • 循环栅栏 CyclicBarrier 用于同一角色线程间的协调一致,所有线程在到达栅栏后都需要等待其他线程,等所有线程都到达后再一起通过。它是循环的,可以用作重复的同步
  • 阻塞队列

    • 对于最常见的生产者/消费者模式,可以使用阻塞队列
    • 阻塞队列封装了锁和条件,生产者线程和消费者线程只需要调用队列的入队/出队方法就可以了,不需要考虑同步和协作问题
    • 阻塞队列有普通的先进先出队列,包括基于数组ArrayBlockingQueue基于链表LinkedBlockingQueue/LinkedBlockingDeque,也有基于堆的优先级阻塞队列 PriorityBlockingQueue,还有可用于定时任务的延时阻塞队列 DelayQueue,以及用于特殊场景的阻塞队列 SynchronousQueueLinkedTransferQueue
  • Future/FutureTask

    • 在常见的主从协作模式中,主线程往往是让子线程异步执行一项任务,获取其结果。手工创建子线程的写法往往比较麻烦,常见的模式是使用异步任务执行服务:不再手工创建线程,而只是提交任务,提交后马上得到一个结果,但这个结果不是最终结果,而是一个 FutureFuture 是一个接口,主要实现类是 FutureTask
    • Future 封装了主线程和执行线程关于执行状态结果的同步,对于主线程而言,它只需要通过 Future 就可以查询异步任务的状态、获取最终结果、取消任务等,不需要再考虑同步和协作问题
-------------------- 本文结束感谢您的阅读 --------------------