Java唤醒阻塞线程的多种方法(附带示例)
在 Java 中,当线程由于调用了某些阻塞操作(如 wait()、join()、sleep() 方法)或因等待同步资源而被阻塞时,可以使用特定的方法来唤醒它。
下面我们详细介绍几种唤醒阻塞线程的方法和实现示例。
注意,signal() 或 signalAll() 在使用时必须用 lock() 和 unlock() 方法包裹,否则会在运行时抛出 IIlegalMonitorStateException 异常。
使用 LockSupport 的 park() 和 unpark(thread) 方法可以阻塞和唤醒线程,而不需要线程拥有某个对象的锁,这使得它比 Object 的 wait() 和 notify() 方法更加灵活。
上面介绍的每种方法都有各自的特点,适用于不同的场景,所以在实际应用中需要根据具体的阻塞情况来选择合适的方法。
下面我们详细介绍几种唤醒阻塞线程的方法和实现示例。
1) notify()或notifyAll()方法
当线程调用了对象的 wait() 方法后进入等待状态时,可以使用相同对象上的 notify() 或 notifyAll() 方法来唤醒线程。public class WaitNotifyExample { final static Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread waitingThread = new Thread(() -> { synchronized (lock) { try { System.out.println("Thread is going to wait."); lock.wait(); System.out.println("Thread is woken up."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); waitingThread.start(); Thread.sleep(1000); // 确保线程开始等待 synchronized (lock) { lock.notify(); // 唤醒lock对象上等待的单个线程 // lock.notifyAll(); // 唤醒lock对象上所有等待的线程 } } }notify()方法用于唤醒lock对象上等待的单个线程。如果有多个线程在等待,则只有其中的一个线程能被唤醒,具体唤醒哪一个线程由虚拟机决定。使用notifyAll()方法可以唤醒所有等待的线程。
2) 使用Condition的signal()或signalAll()方法
当线程在某个条件设置等待时可以使用 Condition 的 await() 方法,然后可以使用 Condition 的 signal() 或 signalAll() 方法来唤醒线程。import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockConditionExample { private static final Lock lock = new ReentrantLock(); private static final Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread waitingThread = new Thread(() -> { lock.lock(); try { System.out.println("Thread is going to await."); condition.await(); // 等待在Condition上 System.out.println("Thread is woken up."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } }); waitingThread.start(); Thread.sleep(1000); // 确保线程开始等待 lock.lock(); try { condition.signal(); // 唤醒一个等待在Condition上的线程 // condition.signalAll(); // 唤醒所有等待在Condition上的线程 } finally { lock.unlock(); } } }如果使用 JUC 中 Condition 的 await() 方法阻塞线程,可以使用 signal() 或 singnalAll() 方法来唤醒线程。需要使用 lock 对象的 newCondition() 方法获得 Condition 对象(可获得多个),Condition 实现了公平锁,默认是非公平锁。
注意,signal() 或 signalAll() 在使用时必须用 lock() 和 unlock() 方法包裹,否则会在运行时抛出 IIlegalMonitorStateException 异常。
3) 使用interrupt()方法
对于因调用了 sleep()、join() 而处于阻塞状态的线程,可以使用 interrupt() 方法来中断线程,这会导致线程抛出 InterruptedException 并返回运行状态。public class InterruptExample { public static void main(String[] args) throws InterruptedException { Thread sleepingThread = new Thread(() -> { try { System.out.println("Thread is going to sleep."); Thread.sleep(10000); // 线程将会睡眠10s } catch (InterruptedException e) { System.out.println("Thread is interrupted while sleeping."); Thread.currentThread().interrupt(); } }); sleepingThread.start(); Thread.sleep(1000); // 确保线程开始睡眠 sleepingThread.interrupt(); // 中断睡眠线程,导致线程抛出 InterruptedException并返回运行状态 } }
4) 利用Semaphore类
Semaphore 可以控制同时访问某个资源的线程数。线程可以通过 acquire() 方法获取许可,如果没有就等待,而 release() 方法用于释放许可。如果线程因为等待获取 Semaphore 上的许可而被阻塞,可以通过释放一个许可来唤醒它。import java.util.concurrent.Semaphore; public class SemaphoreExample { private static final Semaphore semaphore = new Semaphore(0); public static void main(String[] args) { Thread waitingThread = new Thread(() -> { try { System.out.println("Thread is going to acquire."); semaphore.acquire(); // 线程会等待获取许可 System.out.println("Thread got the permit."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); waitingThread.start(); System.out.println("Main thread is going to release a permit."); semaphore.release(); // 释放许可 } }
5) 利用LockSupport类
LockSupport 是 JUC 中的一个工具类,它提供了基本的线程同步功能。使用 LockSupport 的 park() 和 unpark(thread) 方法可以阻塞和唤醒线程,而不需要线程拥有某个对象的锁,这使得它比 Object 的 wait() 和 notify() 方法更加灵活。
public class LockSupportExample { public static void main(String[] args) throws InterruptedException { final Thread thread = new Thread(() -> { System.out.println(Thread.currentThread().getName() + ":被阻塞"); LockSupport.park(); // 阻塞当前线程 if (Thread.interrupted()) { // 检查中断标志 System.out.println(Thread.currentThread().getName() + ":被中断"); } else { System.out.println(Thread.currentThread().getName() + ":被唤醒"); } }); thread.start(); Thread.sleep(2000); // 确保线程已经启动并且被阻塞 System.out.println(Thread.currentThread().getName() + ":准备唤醒线程"); LockSupport.unpark(thread); // 唤醒被阻塞的线程 } }在上述的代码中,子线程会通过 LockSupport 的 park() 方法阻塞自己。主线程在等待 2s 后,通过调用 LockSupport 的 unpark(thread) 方法来唤醒被阻塞的线程。
上面介绍的每种方法都有各自的特点,适用于不同的场景,所以在实际应用中需要根据具体的阻塞情况来选择合适的方法。