如何实现:func1等待时自身不可重入,但允许func2进入同步块唤醒它
嗨,咱们来一步步解决这个问题~你的核心需求很明确:当func1进入等待状态时,要挡住其他线程进入func1,但同时得让func2能正常进入同步逻辑,唤醒等待的func1。先聊聊你原来的思路问题,再给你推荐更优雅的实现方式。
首先分析你最初的代码:
第一个版本里,func1用synchronized(x1)包裹,当调用x1.wait()时,会释放x1的锁——这就导致其他线程可以获取x1锁,再次进入func1的同步块,直接打破了你要的“不可重入”要求,而且isWaiting这个标志位还可能被多个线程乱改,出现逻辑错误。
你后来想到的嵌套同步块思路,虽然用x2锁试图挡住其他线程进入func1,但嵌套同步块确实是个风险点,很容易因为锁的获取顺序不当触发死锁,而且代码可读性也差,确实不是最优解。
接下来给你推荐用java.util.concurrent.locks包里的工具类来实现,这比原生的synchronized+wait/notify灵活太多,完全不需要嵌套锁:
我们可以用**Semaphore来控制func1的入口(保证同一时间只有一个线程能进入func1,哪怕它处于等待状态),再用ReentrantLock+Condition**来实现安全的等待/唤醒逻辑,这样既能满足func1的不可重入要求,又能让func2正常执行唤醒操作。
具体代码如下:
import java.util.concurrent.Semaphore; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Xyz { // 用Semaphore(1)保证func1同一时间只能有一个线程进入(其他线程不可重入) private final Semaphore func1EntryLock = new Semaphore(1); private final ReentrantLock lock = new ReentrantLock(); private final Condition waitCondition = lock.newCondition(); private boolean isWaiting = false; public void func1() throws InterruptedException { // 先获取入口许可,其他线程进不来,直到当前线程释放许可 func1EntryLock.acquire(); try { lock.lock(); try { // 这里执行你需要同步的逻辑 doHereSomethingSynchronized(); // 满足条件时进入等待 if (/* condition1 == expectedValue */ 1 == 1) { isWaiting = true; // await会释放lock,让func2可以获取lock执行唤醒 waitCondition.await(); } } finally { lock.unlock(); } } finally { // 不管有没有等待,最终都释放入口许可,让下一个线程可以进入func1 func1EntryLock.release(); } } public void func2() { lock.lock(); try { // 执行func2需要同步的逻辑 doHereSomethingMoreSynchronized(); if (isWaiting) { waitCondition.signalAll(); isWaiting = false; } } finally { lock.unlock(); } } private void doHereSomethingSynchronized() { // 你的同步逻辑实现 } private void doHereSomethingMoreSynchronized() { // 你的另一段同步逻辑实现 } }
代码逻辑说明:
func1EntryLock(Semaphore(1)):它就像一个“入口门禁”,只有拿到许可的线程才能进入func1。哪怕这个线程后来调用waitCondition.await()释放了lock,它依然持有func1EntryLock的许可,其他线程无法acquire()到许可,自然就进不了func1,完美满足你要的“不可重入”要求。ReentrantLock+Condition:用来替代原生的synchronized+wait/notify,waitCondition.await()会释放lock,这样func2就能获取lock执行同步逻辑和唤醒操作,唤醒后func1的线程会重新获取lock,继续执行后续逻辑。- 所有锁都用
try-finally包裹,确保锁一定会被释放,避免死锁风险。
如果你想要的是严格的不可重入(包括同一个线程也不能重复进入func1),Semaphore也能满足需求——因为它不区分线程,同一个线程多次acquire()也会被挡住,完全符合你的要求。
这个方案既避免了嵌套同步块的风险,又清晰地实现了你的需求,代码可读性和可维护性都比原来的思路好很多。
备注:内容来源于stack exchange,提问作者sam sus

