Потоки Java: методы ожидания и уведомления - PullRequest
3 голосов
/ 25 февраля 2011

У меня есть поток, который вызывает метод 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();
        }
    }
}

В приведенном выше коде, если блок synchronizedв main, если ThreadA не выполняется первым, а другой блок синхронизации выполняется и завершается до завершения, тогда ThreadA выполняет свой блок synchronized и вызывает wait, что произойдет и как это происходитбудет снова уведомлен?

Ответы [ 7 ]

10 голосов
/ 25 февраля 2011

Если ThreadB проходит через свой блок synchronized раньше, чем ThreadA, то ThreadA будет блокироваться на неопределенный срок при вызове wait.Как-то не будет уведомлено, что другой поток уже завершен.

Проблема в том, что вы пытаетесь использовать wait и notify способами, которые не предназначены для использования.Обычно wait и notify используются для того, чтобы один поток ожидал выполнения некоторого условия, а затем чтобы другой поток сигнализировал о том, что условие могло стать истинным.Например, они часто используются следующим образом:

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

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

    /* Consume the resource. */
}

Причина, по которой работает приведенный выше код, заключается в том, что не имеет значения, какой поток запускается первым.Если поток производителя создает ресурс, и никто не wait использует obj, то при запуске потребителя он входит в цикл while, замечает, что ресурс создан, и затем пропускает вызов на * 1018.*.Затем он может потреблять ресурс.Если, с другой стороны, потребитель запускается первым, в цикле while он заметит, что ресурс еще не доступен, и wait уведомит некоторый другой объект.Затем можно запустить другой поток, создать ресурс и notify потребительский поток, для которого доступен ресурс.Как только исходный поток будет пробужден, он заметит, что условие цикла больше не выполняется и будет потреблять ресурс.

В более общем случае Java предлагает всегда вызывать wait в цикле из-за ложных уведомлений , в которых поток может проснуться от вызова на wait, даже не будучи уведомленным ни о чем.Использование описанного выше шаблона может предотвратить это.

В вашем конкретном случае, если вы хотите убедиться, что ThreadB завершил работу до выполнения ThreadA, вы можете использовать Thread.join(), который явно блокируетвызывающий поток, пока не выполнится какой-то другой поток.В более общем случае вы можете захотеть взглянуть на некоторые из других примитивов синхронизации, предоставляемых Java, поскольку их часто гораздо проще использовать, чем wait и notify.

1 голос
/ 25 февраля 2011

Возможно выполнение метода выполнения ThreadB до того, как вы введете синхронизированный блок в ThreadA.main. В этой ситуации, поскольку вызов оповещения произошел до того, как вы начали ждать, ThreadA навсегда заблокирует ожидающий вызов.

Простой обходной путь - захват блокировки b в main перед запуском второго потока, чтобы гарантировать, что ожидание произойдет первым.

ThreadB b = new ThreadB();
synchronized(b) {
    b.start();
    ...
    b.wait();
}
1 голос
/ 25 февраля 2011

Вы можете выполнить цикл и подождать, пока общая сумма не будет вычислена:

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

Вы также можете использовать абстракцию более высокого уровня, например CountDownLatch .

0 голосов
/ 19 февраля 2014

Зачем делать этот комплекс? Просто используйте функцию join () Thread.

ThreadB b = new ThreadB();
b.start();
b.join();
// now print b.total
0 голосов
/ 25 февраля 2011

1) Вам необходимо добавить некоторый флаг, который используется для связи между потоками, чтобы B мог сигнализировать A, когда он закончил.Простая логическая переменная - это хорошо, если она только для чтения и записи в синхронизированных блоках.

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

2) Необходимо выполнить цикл во время ожидания.Так что, если ваша логическая переменная была названа isDone и была установлена ​​в true для threadB, тогда threadA должен иметь такой код:

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

В этом конкретном случае, на самом деле нет причин иметь синхронизированный блокA - поскольку threadB ничего не делает после завершения работы, а A ничего не делает, кроме ожидания B, threadA может просто вызвать b.join () для блокировки до завершения.Я предполагаю, что ваш фактический вариант использования более сложный, чем этот.

0 голосов
/ 25 февраля 2011

не synchronized(thread), не делайте этого, не synchronized(thread) .. repat: no synchronized(thread):)

И если вам нужно дождаться окончания потока 'b', используйте b.join (), теперь ваш код может свободно висеть в b.wait ()

-

Надеемся, что приведенный ниже источник может дать вам понимание при синхронизации (поток) /notify () Я считаю плохую практику.(cut-cut)

Наслаждайтесь


Чтобы продолжить, вы убедитесь, что приняли лицензионное соглашение Oracle, найденное там: https://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/ViewLicense-Start?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, миллис == 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 вызывается после остановки, обеспечивающей надлежащие привилегии

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;
    }
}
0 голосов
/ 25 февраля 2011

Вы, вероятно, хотите использовать для этого java.util.concurrent.Semaphore.

...