Wait() / notify() synchronization



我正在尝试检查等待/通知在java中是如何工作的。

代码:

public class Tester {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();
        synchronized (t) {
            try {
                System.out.println("wating for t to complete");
                t.wait();
                System.out.println("wait over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("entering run method");
        synchronized (this) {
            System.out.println("entering syncronised block");
            notify();
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("leaving syncronized block");
        }
        System.out.println("leaving run method");
    }
}

返回的输出

wating for t to complete
entering run method
entering syncronised block
//sleep called
leaving syncronized block
leaving run method
wait over

我原以为当notify()被执行时,等待将结束&将打印System.out.println("wait over");。但它似乎只有在t完成其run()时才被打印出来。

对象监视器锁需要执行同一锁的单个引用。。。

在您的示例中,您是Thread实例上的waiting,但使用Runnable中的notify。相反,您应该使用一个单独的、通用的锁对象。。。例如

public class Tester {
    public static final Object LOCK = new Object();
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();
        synchronized (LOCK) {
            try {
                System.out.println("wating for t to complete");
                LOCK.wait();
                System.out.println("wait over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static class MyRunnable implements Runnable {
        public void run() {
            System.out.println("entering run method");
            synchronized (LOCK) {
                System.out.println("entering syncronised block");
                LOCK.notify();
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("leaving syncronized block");
            }
            System.out.println("leaving run method");
        }
    }
}

输出

wating for t to complete
entering run method
entering syncronised block
leaving syncronized block
wait over
leaving run method

CCD_ 8和CCD_ 9可以根据线程调度来改变位置。

你可以试着把睡眠放在synchronized块的一边。这将释放监视器锁定,允许wait部分继续运行(因为在锁定释放之前无法启动)

    public static class MyRunnable implements Runnable {
        public void run() {
            System.out.println("entering run method");
            synchronized (LOCK) {
                System.out.println("entering syncronised block");
                LOCK.notify();
                System.out.println("leaving syncronized block");
            }
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("leaving run method");
        }
    }

对更新代码的回答:

来自Thread.sleep()javadoc:

使当前正在执行的线程为指定的毫秒数,取决于系统计时器的精度和准确性以及调度器线程不会失去对任何监视器的所有权

如果在同步块内调用Thread.sleep,其他线程将无法进入同步块。为了避免这种情况,您永远不应该在同步块中执行耗时的任务。

请注意(正如其他人所指出的),您必须在两个线程中使用相同的对象进行锁定/同步。

如果希望主线程在调用notify后立即继续,则必须暂时放弃锁定。否则,wait将仅在次线程离开synchronized块之后被调用。在长时间运行的计算中保持锁定从来都不是一个好主意!

实现的一种方法是在锁上使用wait(int)而不是sleep,因为wait暂时释放同步锁:

public class Tester {
    private static final Object lock = new Object();
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        synchronized (lock) {
            try {
                System.out.println("wating for t to complete");
                lock.wait();
                System.out.println("wait over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyRunnable implements Runnable {
        public void run() {
            System.out.println("entering run method");
            synchronized (lock) {
                System.out.println("entering syncronised block");
                lock.notify();
                try {
                    lock.wait(1000); // relinquish the lock temporarily
                } catch (InterruptedException ex) {
                    System.out.println("got interrupted");
                }
                System.out.println("leaving syncronized block");
            }
            System.out.println("leaving run method");
        }
    }
}

然而,使用这些低级原语可能非常容易出错,我不鼓励使用它们。相反,我建议您使用Java的高级原语来实现这一点。例如,您可以使用CountDownLatch,它让一个线程等待,直到其他线程倒计时为零:

import java.util.concurrent.*;
public class TesterC {
    private static final CountDownLatch latch = new CountDownLatch(1);
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        System.out.println("wating for t to complete");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("wait over");
    }
    static class MyRunnable implements Runnable {
        public void run() {
            System.out.println("entering run method");
            try {
                latch.countDown();
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                System.out.println("got interrupted");
            }
            System.out.println("leaving run method");
        }
    }
}

在这里,你不必同步任何东西,闩锁为你做一切。您还可以使用许多其他原语-信号量、交换器、线程安全队列等。请浏览java.util.concurrent包。

也许更好的解决方案是使用更高级别的API,就像Akka提供的那样。在那里,您可以使用Actors或Software事务性内存,它可以很容易地组合,并避免大多数并发问题。

最新更新