3

我有一个调用该wait方法的线程,并且只能在notify从其他类调用该方法时被唤醒:

 class ThreadA {
     public static void main(String [] args) {
         ThreadB b = new ThreadB();
         b.start();

         synchronized(b) {
             try {
                 System.out.println("Waiting for b to complete...");
                 b.wait();
             } catch (InterruptedException e) {}
             System.out.println("Total is: " + b.total);
         }
     }
 }

class ThreadB extends Thread {
    int total;
    public void run() {
        synchronized(this) {
            for(int i=0;i<100;i++) {
                total += i;
            }
            notify();
        }
    }
}

在上面的代码中,如果中的synchronizedmain,如果ThreadA没有先执行,而是另一个同步块执行并完成完成,然后ThreadA执行它的synchronized块并调用wait,会发生什么以及如何再次通知它?

4

7 回答 7

10

如果之前ThreadB通过了它的synchronized阻塞ThreadA,那么ThreadA将在调用时无限期阻塞wait。它不会以某种方式被通知另一个线程已经完成。

问题是您正在尝试使用它们waitnotify以非设计使用的方式使用它们。通常,waitandnotify用于让一个线程等待某个条件为真,然后让另一个线程发出信号表明该条件可能已变为真。例如,它们通常按如下方式使用:

/* Producer */
synchronized (obj) {
    /* Make resource available. */
    obj.notify();
}

/* Consumer */
synchronized (obj) {
    while (/* resource not available */)
        obj.wait();

    /* Consume the resource. */
}

上述代码有效的原因是哪个线程先运行并不重要。如果生产者线程创建了一个资源并且没有人在调用,那么当消费者运行时,它会进入循环,注意到资源已经产生,然后跳过对 的wait调用。然后它可以消耗资源。另一方面,如果消费者首先运行,它会在循环中注意到资源尚不可用,并让其他对象通知它。然后另一个线程可以运行,产生资源,以及资源可用的消费者线程。一旦原来的线程被唤醒,它会注意到循环的条件不再为真,并且会消耗资源。objwhilewaitwhilewaitnotify

更一般地说,Java 建议您始终wait在循环中调用,因为虚假通知在其中线程可以从调用中唤醒,wait而不会收到任何通知。使用上述模式可以防止这种情况。

在您的特定实例中,如果您想确保在执行ThreadB之前已完成运行ThreadA,您可能需要使用Thread.join(),它显式阻止调用线程,直到其他线程执行。更一般地说,您可能想研究 Java 提供的其他一些同步原语,因为它们通常比wait和更容易使用notify

于 2011-02-25T18:46:14.227 回答
1

您可以循环并等到总数被计算出来:

synchronized(b) {
   while (total == 0) {
       b.wait();
   }
}

您还可以使用更高级别的抽象,例如CountDownLatch

于 2011-02-25T18:42:36.467 回答
1

ThreadB 的 run 方法有可能在进入 ThreadA.main 中的同步块之前完成。在这种情况下,由于在您开始等待之前发生了通知调用,因此 ThreadA 将在等待调用上永远阻塞。

一个简单的解决方法是在启动第二个线程之前在 main 中获取 b 上的锁,以确保首先发生等待。

ThreadB b = new ThreadB();
synchronized(b) {
    b.start();
    ...
    b.wait();
}
于 2011-02-25T18:46:22.600 回答
0

您可能想为此使用 java.util.concurrent.Semaphore。

于 2011-02-25T18:41:33.790 回答
0

1)您需要添加一些用于线程之间通信的标志,以便 B 可以在完成时向 A 发出信号。一个简单的布尔变量就可以了,只要它只能在同步块中读取和写入。

synchronized(this) {
    for(int i=0;i<100;i++) {
        total += i;
    }
    isDone = true;
    notify();
}

2) A 需要在等待时循环。因此,如果您的布尔变量被称为 isDone,并由 threadB 设置为 true,那么 threadA 应该有一些如下代码:

synchronized(b) {
    System.out.println("Waiting for b to complete...");
    while( ! isDone ) b.wait();
}

在这种特殊情况下,实际上没有理由在 A 中使用同步块 - 因为 threadB 在完成运行后不做任何事情,而 A 除了等待 B 之外什么也不做,threadA 可以简单地调用 b.join()阻止直到完成。我假设您的实际用例比这更复杂。

于 2011-02-25T18:46:06.020 回答
0

为什么要这么复杂?只需使用 Thread 的 join() 函数即可。

ThreadB b = new ThreadB();
b.start();
b.join();
// now print b.total
于 2014-02-19T19:02:38.680 回答
-1

不要synchronized(thread),不要这样做,不要synchronized(thread)..重复:不synchronized(thread):)

如果您需要等待线程“b”完成,请使用 b.join(),现在您的代码可以自由挂在 b.wait() 中

--

希望下面的来源可以让您了解同步(线程)/通知()我认为不好的做法。(切切)

享受


要在下面继续,请确保您已接受 Oracle 的许可协议,该协议位于: https ://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/ViewLicense-开始?LicenseUUID=7HeJ_hCwhb4AAAEtmC8ADqmR&ProductUUID=pGqJ_hCwj_AAAAEtB8oADqmS&cnum=&evsref=&sln=

Java 源代码(包括),在 init() 中调用,由任何 java c-tor 有效调用,自 java 1.5 起

private static **synchronized int** nextThreadNum() {
return threadInitNumber++;
}

//join(使用 nanos 的方法仅将毫秒加一,如果 nanos>500000,millis==0 且 nanos>0

public final **synchronized** void join(long millis) 
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
    while (isAlive()) {
    wait(0);
    }
} else {
    while (isAlive()) {
    long delay = millis - now;
    if (delay <= 0) {
        break;
    }
    wait(delay);
    now = System.currentTimeMillis() - base;
    }
}
}


public **synchronized** void start() {
    /**
 * This method is not invoked for the main method thread or "system"
 * group threads created/set up by the VM. Any new functionality added 
 * to this method in the future may have to also be added to the VM.
 *
 * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    start0();
    if (stopBeforeStart) {
    stop0(throwableFromStop);
}
}

//stop1 在 stop 确保正确的权限后调用

private final **synchronized** void stop1(Throwable th) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
    checkAccess();
    if ((this != Thread.currentThread()) ||
    (!(th instanceof ThreadDeath))) {
    security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
    }
}
    // A zero status value corresponds to "NEW"
if (threadStatus != 0) {
    resume(); // Wake up thread if it was suspended; no-op otherwise
    stop0(th);
} else {

        // Must do the null arg check that the VM would do with stop0
    if (th == null) {
    throw new NullPointerException();
    }

        // Remember this stop attempt for if/when start is used
    stopBeforeStart = true;
    throwableFromStop = th;
    }
}
于 2011-02-25T18:43:20.827 回答