7.线程协作
线程通信
应用场景:生产者消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的商品放入仓库,消费者取走仓库中的产品。
- 如果仓库中没有产品,生产者将产品放入仓库,否则停止生产并等待,直到仓库中产品被消费者取走。
- 如果仓库中放有产品,消费者可以将产品取走,否则消费者停止消费并等待,直到仓库中有产品为止。
这是一个线程同步问题,生产者和消费者共享同一资源,并且生产者和消费者之间相互依赖,互为条件。
- 对于生产者,没有生产 产品之前,要通知消费者等待。生产了之后,又要通知消费者 消费。
- 对于消费者,消费之后,要通知生产者生产新的产品。
- 在生产者消费者问题中,仅有synchronized是不够的。
- synchronized 可阻止并发更新同一共享资源,实现了同步。
- synchronized 不能用来实现不同线程之间的通信。
Java提供了几个方法,用来解决线程间通信问题:
方法 | 作用 |
---|---|
wait() | 表示线程一直等待,直到接到其他线程通知。 与sleep() 不同,会释放锁。 |
wait(long timeout) | 指定等待的毫秒数。 |
notify() | 唤醒一个处于等待状态的线程。 |
notifyAll() | 唤醒同一个对象上所有调用wait() 方法的线程。 优先级高的线程 优先调度。 |
注意: 这些均是Object类中的方法,都只能在同步方法 或 同步代码块中使用,否则会抛出异常lllegalMonitorStateException.
解决方法1:管程法
- 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
- 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓存区。
生产者将生产的数据放入缓冲区,消费者从缓冲区拿数据。
解决方法2:信号灯法
- 用一个标志位。
1.管程法
1 | // 管程法:需要生产者、消费者、产品、缓冲区 |
2.信号灯法
1 | /** |
结果:
1 | 表演了->直播胡扯 |
使用线程池
- 背景:经常创建和销毁、使用容量特别大的资源。在并发的情况下的线程,对性能影响非常大。
- 思路:提前创建好多个线程,放入线程池中。使用时直接获取,使用完再放回池中。可避免频繁创建销毁,实现了重复利用。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:线程池大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时,最多保持多长时间后 终止
ExecutorService 与 Executors:
ExecutorService
:真正的线程池接口。常用子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,一般用来执行Runnable
- <T>Future<T> submit(Callable<T> task):执行任务,一般用来执行Callable
- void shutdown():关闭线程池
Executors
:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
测试:
1 | public class TestPool { |
结果:
1 | pool-1-thread-1 |