JAVA架构师之路
本系列文章均是博主原创,意在记录学习上的知识,同时一起分享学习心得。
第一章
第一节 Java集合总结(架构师之路 )
第二节 Java多线程(架构师之路 )
第三节 理解synchronized(架构师之路 )
文章目录
前言
多线程之间除了竞争关系之外,还存在互相协作的情况。本章介绍了多线程基本机制wait/notify。
一、协作场景
多线程之间的协作场景有很多,比如:
- 生产者/消费者协作模式:生产者和消费者线程通过共享队列进行协作,当队列满时生产者需要等待,队列空时消费者需要等待。
- 同时开始:要求多个程序需要同时开始。
- 等待结束:通常用于主从协作模式,主线程将任务分解为多个子线程,主线程需要等待子线程都执行完毕,才执行其他任务。
- 异步结果:将子线程的管理封装为异步调用,异步调用马上返回,但不是返回最终结果,而是一个称为Future的对象,通过它可以在随后获得最终的结果。
- 集合点:在并行迭代计算中,每个线程负责一部分计算,然后在集合点等待其他线程完成,所有线程到齐后,交换数据和计算结果,再进行下一次迭代。
二、wait/notify
Java的根父类都是Object,其定义了一些线程协作的基本方法,大致分为两类:wait、notify。
1.wait
主要有两个方法:
- wait()。表示无限期等待,实际调用的是wait(0)。
- wait(long timeout)。表示等待timeout长时间,单位是毫秒。
具体过程:
1)把当前线程放入条件等待队列,释放对象锁,阻塞等待,线程状态变为WAITING或TIMED_WAITING。
2)等待时间到或被其他线程调用notify/notifyAll从条件队列中移除,这时要重新竞争对象锁:
- 如果能够获得对象锁,线程状态变为RUNNABLE,并从wait()调用中返回。
- 否则,该线程加入对象锁等待队列,线程状态变为BLOCKED,只有在获得锁后才会从wait调用中返回。
2.notify
调用notify会把在条件队列中等待的线程唤醒并从队列中移除,但它不会释放对象锁,也就是说,只有包含notify的synchronized代码块执行完后,等待的线程才会从wait调用中返回。
主要有两个方法:
- notify()。表示从条件队列中选一个线程,将其从队列中移除并唤醒。
- notifyAll()。表示移除条件队列中所有的线程并全部唤醒。
3、协作过程
每个对象其实都有一个锁、和一个锁的等待队列,除此之外,每个对象还有另外一个等待队列,叫条件队列,该队列用于线程间的协作。
- 调用wait()就会把当前线程放到条件队列上并阻塞,表示当前线程执行不下去了,它需要等待一个条件,改条件它自己改不了,需要其他线程改变。
- 当其他线程调用notify/notifyAll时,会改变条件,唤醒在等待队列中的线程。
示例代码:
public class WaitThread extends Thread{
private volatile boolean fire = false;
@Override
public void run() {
try {
// 子线程A获取waitThread对象的锁
synchronized (this) {
while (!fire) {
wait();// 子线程释放waitThread对象的锁,并加入waitThread对象的条件队列
}
}
System.out.println("fired");
} catch (InterruptedException e) {
}
}
public synchronized void fire() {
this.fire = true;
// 从waitThread对象的条件队列中唤醒其中一条子线程A,将其从条件队列中移除,
// 并让子线程A竞争waitThread对象锁
notify();
}
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();//开启子线程A
sleep(1000L);
System.out.println("fire");
waitThread.fire();// 主线程获取waitThread对象的锁,并执行fire()方法
}
}
【注意】
1、wait/notify只能在synchronized代码块内被调用,如果调用wait/notify时,当前线程没有持有对象锁,会抛出java.lang.IllegalMonitor-StateException。
2、wait/notify它们被不同的线程调用,但共享相同的锁和条件等待队列。它们围绕一个共享的条件变量进行协作,这个条件变量时程序自身维护的。
3、在设计多线程协作时,需要想清楚协作的共享变量和条件是什么,这是协作的核心。
三、应用场景
3.1 生产者/消费者模式
在生产者/消费者协作模式中,其共享变量和条件如下:
共享变量:队列。
条件:
- 生产者往队列添加数据时,如果队列满了需要wait;
- 消费者从队列消费数据时,如果队列为空需要wait;
代码示例:
public class Test {
public static void main(String[] args) {
MyBlockingQueue<String> queue = new MyBlockingQueue<>(10);
new Producer(queue).start();
new Customer(queue).start();
}
}
public class MyBlockingQueue<E> {
private Queue<E> queue = null;
private int limit;
public MyBlockingQueue(int limit) {
this.limit = limit;
queue = new ArrayDeque<>(limit);
}
public synchronized void put(E e) throws InterruptedException {
while (queue.size() == limit) {
wait();
}
queue.add(e);
notifyAll(