-
线程阻塞 (Blocking)
- 发生在当线程尝试执行某个操作(如I/O操作、等待锁等)而无法继续,此时线程会放弃CPU控制权。
- 可分为主动阻塞(如调用
sleep()
、join()
)和被动阻塞(如等待锁)。 - 线程阻塞期间不消耗CPU资源,等待特定条件满足后,由操作系统重新调度执行。
-
线程等待 (Waiting)
- 特指在Java中使用同步机制(如
wait()
方法)时,线程自愿放弃对象的监视器锁并进入等待状态。 - 等待的线程需要被其他线程通过
notify()
或notifyAll()
唤醒,之后才能重新尝试获取锁并继续执行。 - 与阻塞类似,等待期间也不占用CPU资源,但强调线程间通过监视器进行的协调。
- 特指在Java中使用同步机制(如
-
线程挂起 (Suspending)
- 在现代编程实践中,“挂起”一词较少使用,且其含义可能随上下文而变化。
- 传统意义上,挂起意味着暂停线程的执行,通常在调试时使用,但现在已不鼓励直接挂起线程,因为可能导致死锁等问题。
- 与阻塞和等待相比,挂起更多关联于开发者手动控制线程生命周期,而不是程序逻辑自然流程的一部分。
解释:
监视器的工作原理
每个Java对象在内存中除了存储其实际数据外,还包含一个对象头,这个对象头中有一部分被称为Mark Word,它包含了对象的锁状态等信息。当一个线程试图通过synchronized
关键字进入一个同步代码块或方法时,实际上是在尝试获取这个对象监视器的所有权。具体过程大致如下:
-
尝试获取锁:线程执行到
synchronized
代码块或方法时,会尝试获取对象监视器锁。如果锁未被其他线程持有,当前线程就会成功获取锁,并继续执行。 -
进入等待队列:如果锁已经被其他线程持有,那么当前线程会被阻塞,并被放置到这个对象监视器相关的入口集(Entry Set)或等待集(Wait Set)中。入口集存放的是正在尝试获取锁的线程,而等待集存放的是调用了
wait()
方法等待被唤醒的线程。 -
释放锁:持有锁的线程执行完同步代码块或方法,或者因异常退出时,会自动释放锁。此时,如果入口集中有等待的线程,JVM会选择一个(通常是按照一定规则,如FIFO)来给予锁并唤醒它,让其继续执行。
监视器的作用
-
锁的状态管理:监视器确实与对象头中的锁状态信息紧密相关。对象头中记录的Mark Word可以包含锁标志位,指示对象是否被锁定,以及锁的类型(轻量级、偏向、重量级)。这是监视器机制的基础,用来识别和管理锁的占有状态。
-
持有锁的线程标识:在轻量级锁和偏向锁的模式下,对象头中的Mark Word也可能间接或直接包含持有锁的线程ID(通过线程的栈帧指针)。这意味着监视器通过对象头信息来快速识别哪个线程拥有锁,尤其是轻量级锁通过CAS操作直接在对象头中交换线程ID。
-
同步和线程调度:监视器更重要的作用在于提供一种同步机制,包括管理入口集和等待集。尽管这些集合本身不存储在对象头中,但监视器概念涵盖了对这些集合的维护和操作,比如决定哪些线程可以进入同步块(即入口集的管理),以及如何处理调用
wait()
后线程的挂起和唤醒(等待集的管理)。
例子:
public class Buffer {
private int product = 0; // 仓库中的产品数量
private final int capacity = 10; // 仓库容量
// 生产产品的方法
public synchronized void produce() throws InterruptedException {
while (product == capacity) { // 如果仓库满,生产者等待
wait(); // 生产者线程等待
}
product++; // 生产一个产品
System.out.println("Produced: " + product);
notifyAll(); // 通知所有等待的线程(消费者或另一个生产者)
}
// 消费产品的方法
public synchronized void consume() throws InterruptedException {
while (product == 0) { // 如果仓库空,消费者等待
wait(); // 消费者线程等待
}
product--; // 消费一个产品
System.out.println("Consumed: " + product);
notifyAll(); // 通知所有等待的线程(生产者或其他消费者)
}
}
public class Producer implements Runnable {
private Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 0; i < 20; i++) {
buffer.produce();
Thread.sleep(100); // 模拟生产时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class Consumer implements Runnable {
private Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
try {
for (int i = 0; i < 20; i++) {
buffer.consume();
Thread.sleep(150); // 模拟消费时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class Main {
public static void main(String[] args) {
Buffer buffer = new Buffer();
Thread producerThread = new Thread(new Producer(buffer), "Producer");
Thread consumerThread1 = new Thread(new Consumer(buffer), "Consumer 1");
Thread consumerThread2 = new Thread(new Consumer(buffer), "Consumer 2");
producerThread.start();
consumerThread1.start();
consumerThread2.start();
}
}