首页 > 编程笔记 > Java笔记 阅读:1

Java唤醒阻塞线程的多种方法(附带示例)

在 Java 中,当线程由于调用了某些阻塞操作(如 wait()、join()、sleep() 方法)或因等待同步资源而被阻塞时,可以使用特定的方法来唤醒它。

下面我们详细介绍几种唤醒阻塞线程的方法和实现示例。

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) 方法来唤醒被阻塞的线程。

上面介绍的每种方法都有各自的特点,适用于不同的场景,所以在实际应用中需要根据具体的阻塞情况来选择合适的方法。

相关文章