为什么我的notify()没有唤醒等待线程?

时间:2012-07-09 18:57:01

标签: java multithreading

我有一个简单的多线程算法,用于在后台线程中加载一系列文件,并在加载完成后让JPanel显示第一个图像。在JPanel的构造函数中,我启动加载器,然后在图像列表上等待,如下所示:

//MyPanel.java
public ArrayList<BufferedImage> images = new ArrayList<BufferedImage>();
int frame;    

public MyPanel(String dir){
    Thread loader = new thread (new Loader(dir, this));
    loader.start();

    frame = 0;
    //miscellaneous stuff

    synchronized(images){
        while (images.isEmpty()){
            images.wait();
        }
    }

    this.setPrefferedSize(new Dimension(800,800));
}

@Override
public void paintComponent(Graphics g){
    super.paintComponent(g)

    g.drawImage(images.get(frame), 0, 0, 800, 800, null);
}

我的Loader线程看起来像这样:

//Loader.java
String dir;
MyPanel caller;

public Loader(String dir, MyPanel caller){
    this.dir = dir;
    this.caller = caller;
}

@Override
public void run(){
    File dirFile = new File(dir);
    File[] files = dirFile.listFiles();
    Arrays.sort(files);

    for (File f : files) {
        try {
            synchronized (caller.images) {
                BufferedImage buffImage = ImageIO.read(f);
                caller.images.add(buffImage);
                caller.images.notifyAll();
            }
        } catch (IOException e) {
        }
    }
}

我已经验证执行在调用线程唤醒之前多次通过notifyAll()(通常> 20)并在帧中显示图像。我还验证了images对象实际上是与等待的对象相同的对象。我尝试添加yield(),但这没有帮助。为什么调用notifyAll()不会立即唤醒等待线程?

3 个答案:

答案 0 :(得分:5)

试试这个..

1。 wait()立即,释放锁定。

2。 notify() or notifyAll()不立即释放锁定,但会一直到达同步块的结束括号..

3。出于上述目的,在CountDownLatch中使用java.util.concurrent package

答案 1 :(得分:4)

  

我已经验证执行在调用线程唤醒之前多次(通常> 20)通过notifyAll()并在帧中显示图像。

您的加载程序线程正在循环,立即重新启动caller.images上的监视器,可能在它放弃其时间片之前。

等待线程必须重新获取监视器才能取得进展 - 由于加载程序再次抓取它,它无法做到。

你在这里尝试实现的目标并不是很清楚,但是开始一个新线程然后在构造函数中等待通常是一个非常糟糕的主意。哎呀,如果在第一张图片加载之前你根本无法做任何事情,为什么不同步呢?无论如何,这就是你有效所做的......

答案 2 :(得分:4)

“简单的多线程算法”是矛盾的。线程总是很难。使用来自java.util.concurrent的抽象(它们也很难,但有些人认为它们可以管理),除非你的名字是Brian Goetz。

代码中的两个大错误是:

  • 首先,有可能加载程序在Swing执行之前获取锁定,并且在结束之前它不会将其返回(是的,它会偶尔放弃它,但它是可能会再次抓住它)

  • 因为您的JPanel构造函数是在EDT中调用的(如果不是,那么您会有更大的错误),paintComponent将不会被调用,直到构造函数完成(只有一件事可以在Swing中发生)一旦)。因此,无论如何你都无法实现你想要的目标。

还有更多,但如果你解决了上述两个问题,其余部分将无关紧要。

我建议您删除整个等待的内容并且:

  • 使列表同步(使用images = Collections.synchronizedList(new ArrayList()););
  • 在加载程序中添加调用caller.repaint(),而不是images.notifyAll()

更新:我刚刚在其中一条评论中读到,您的意图是在第一张图片加载之前实际停止EDT。在这种情况下,你的整个目标都是错误的:阻止EDT是一个重大错误。