1. 线程等待唤醒机制
在JUC(Java Util Concurrent)并发编程中,线程等待唤醒机制是实现线程之间协作和同步的重要手段。这种机制允许一个线程挂起等待某个条件满足后被唤醒,以及另一个线程在满足某个条件后唤醒等待的线程。在Java中,有多种实现线程等待唤醒机制的方式,包括使用Object的wait()和notify()方法、Condition接口以及LockSupport类。
1.1 使用Object的wait()和notify()方法
Object类是Java中所有类的父类,在多线程编程中,我们可以利用Object的wait()和notify()方法实现线程等待唤醒机制。这种方式需要在同步代码块中进行操作,因为wait()和notify()方法必须在同步块中调用。
public class WaitNotifyExample { public static void main(String[] args) { final Object lock = new Object(); Thread waitingThread = new Thread(() -> { synchronized (lock) { System.out.println("Waiting thread: Started waiting..."); try { lock.wait(); // 线程等待,释放锁 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Waiting thread: Resumed!"); } }); Thread notifyingThread = new Thread(() -> { synchronized (lock) { System.out.println("Notifying thread: Started notifying..."); lock.notify(); // 唤醒等待线程 System.out.println("Notifying thread: Finished notifying"); } }); waitingThread.start(); try { Thread.sleep(1000); // 确保等待线程先执行 } catch (InterruptedException e) { e.printStackTrace(); } notifyingThread.start(); } }
在上面的示例中,我们创建了两个线程:waitingThread和notifyingThread。waitingThread首先获取锁并调用lock.wait(),进入等待状态,并释放了锁。接着,notifyingThread获取锁,调用lock.notify()唤醒了等待线程。注意,wait()和notify()方法都必须在synchronized块内部使用,否则会抛出IllegalMonitorStateException。
1.2 使用Condition接口
Condition接口是Java并发包(java.util.concurrent.locks)中的一部分,它提供了类似于Object的wait()和notify()方法的功能,但更加灵活。可以在一个Lock对象上通过Condition来实现线程的等待和唤醒。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { public static void main(String[] args) { final Lock lock = new ReentrantLock(); final Condition condition = lock.newCondition(); Thread waitingThread = new Thread(() -> { lock.lock(); try { System.out.println("Waiting thread: Started waiting..."); condition.await(); // 线程等待 System.out.println("Waiting thread: Resumed!"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); Thread notifyingThread = new Thread(() -> { lock.lock(); try { System.out.println("Notifying thread: Started notifying..."); condition.signal(); // 唤醒等待线程 System.out.println("Notifying thread: Finished notifying"); } finally { lock.unlock(); } }); waitingThread.start(); try { Thread.sleep(1000); // 确保等待线程先执行 } catch (InterruptedException e) { e.printStackTrace(); } notifyingThread.start(); } }
在上述示例中,我们使用了ReentrantLock作为锁,通过调用lock.newCondition()创建了一个与该锁关联的Condition实例。然后,等待线程在等待前调用condition.await()进行等待,而唤醒线程通过condition.signal()来唤醒等待的线程。
1.3 使用LockSupport类
LockSupport类是Java并发包(java.util.concurrent.locks)中的工具类,它提供了一种基于线程的阻塞和唤醒机制,不需要依赖于特定的对象实例。
LockSupport的实现依赖于每个线程持有的一个许可(permit)。线程调用park()方法时,如果它持有许可,则立即返回;否则,将会阻塞(挂起)。线程调用unpark(Thread thread)方法时,如果线程处于挂起状态且持有许可,则会使其恢复运行。如果线程尚未调用park()方法或之前已经调用过unpark()方法,并且还持有许可,则后续的park()调用将不会阻塞。而Object类实现线程等待唤醒机制中先notify再wait就会一直处于阻塞状态,Condition接口同理。
LockSupport使用了类似信号量的机制,只有一个许可可供线程使用。如果一个线程调用了unpark(Thread thread)方法,而目标线程还没有调用park()方法,那么目标线程在之后调用park()方法时将立即返回,而不会阻塞。
在内部实现上,LockSupport使用了一些底层的Java本地接口(JNI)来实现线程的挂起和恢复。它通过修改线程的内部状态来达到挂起和恢复线程的目的。
import java.util.concurrent.locks.LockSupport; public class LockSupportExample { public static void main(String[] args) { Thread waitingThread = new Thread(() -> { System.out.println("Waiting thread: Started waiting..."); LockSupport.park(); // 线程挂起 System.out.println("Waiting thread: Resumed!"); }); Thread notifyingThread = new Thread(() -> { System.out.println("Notifying thread: Started notifying..."); LockSupport.unpark(waitingThread); // 唤醒线程 System.out.println("Notifying thread: Finished notifying"); }); waitingThread.start(); try { Thread.sleep(1000); // 确保等待线程先执行 } catch (InterruptedException e) { e.printStackTrace(); } notifyingThread.start(); } }