2

我有一个简单的多线程算法,旨在在后台线程中加载一系列文件,并让 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.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)。我还验证了图像对象实际上与正在等待的对象是同一个对象。我尝试添加一个yield(),但这没有帮助。为什么调用notifyAll()不立即唤醒等待线程?

4

3 回答 3

5

尝试这个..

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

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

3.用于您CountDownLatchjava.util.concurrent package上述目的。

于 2012-07-09T19:02:18.280 回答
4

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

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

等待线程必须重新获取监视器才能取得进展——它不能这样做,因为加载程序再次抓取了它。

目前尚不清楚您要在这里实现什么,但是启动一个新线程然后在构造函数中等待通常是一个非常糟糕的主意。哎呀,如果在第一个图像加载之前你什么都做不了,为什么不同步做呢?无论如何,这就是你正在有效地做的事情......

于 2012-07-09T19:00:40.090 回答
4

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

您的代码中的两个大错误是:

  • 首先,装载程序有可能在 Swing 之前获得了锁,并且在它完成之前它不会把它还给它(是的,它偶尔会放弃它,但它有可能会抓住它再次)

  • 由于您的 JPanel 构造函数是在 EDT 中调用的(如果不是,那么您会遇到更大的错误),因此在构造函数完成之前不会调用paintComponent(在 Swing 中一次只能发生一件事)。所以无论如何你都不会达到你想要的。

还有更多,但如果你修复了上面的两个,剩下的就无关紧要了。

我建议你删除整个等待的东西,然后:

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

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

于 2012-07-09T19:11:30.553 回答