【问题标题】:Java paint 'stuttering' -list concurrencyJava 绘制“口吃”列表并发
【发布时间】:2014-10-03 16:53:30
【问题描述】:

我的一个应用程序通过读取数组 List 将对象绘制到屏幕上:

简单代码总结:

@Override
public synchronized void paintComponent(Graphics g) {
  for(Object gO:paintList) {
    g.drawImage( gO.getObjImage(), gO.getXLoc(), gO.getYLoc(), outer.getTransparent(), null);
  }
}

问题是每次用户单击鼠标时我都会添加更多对象,因此如果用户单击速度足够快,我可能会导致程序绘制卡顿,因为它在写入时无法读取(arrayList 是同步的)。开发人员用于处理此并发问题的常见做法是什么?

编辑:这里是调用重绘的代码:

byte ticks = 0;
    while(true) {
        currentTime = System.nanoTime();
        if(ticks == 25) {
            drawPanel.repaint();
            ticks = 0;
        } else if (ticks%5 == 0) {//if ticks is a multiple of 5 (5,10,15,...)
            drawPanel.operations();
            ticks++;
        } else if(ticks < 25) {
            ticks++;
        }
        try {
            /*
            Refer to: 'http://stackoverflow.com/questions/1036754/difference-between-wait-and-sleep'
            on differences between Thread.sleep() and wait()
            */
            wait(1);//old timings: (long)(refreshRate*1000)
        } catch (InterruptedException ex) {
            Logger.getLogger(DeathWish.class.getName()).log(Level.SEVERE, null, ex);
        }
        //Debugging
        //System.out.println(ticks);
        currentTime = System.nanoTime();

*where operations() 计算“可绘制”对象属性的变化,删除满足特定条件的对象并将新对象添加到绘制列表中。从逻辑上讲,添加和写入应该分开吗? 如果没有足够的信息,我可以发布操作()方法,但我尽量不发布大量代码,以便更容易解释。

【问题讨论】:

  • 一般来说,我有一个循环连续绘制屏幕,​​另一个循环处理任何事件(如单击以添加对象)。这样,如果事件处理程序卡在某个东西上,绘制循环将继续运行而不会卡顿。
  • 我真的怀疑用户点击的速度是否比获得锁的速度更快。锁定获取时间约为 25ns。用户点击时间约为 50 毫秒。
  • 你有什么例外吗?
  • @Dimitri 不,代码不会抛出任何异常,它曾经抛出 concurrentModificationException 但我不久前重组了代码。
  • 您在@swap905 下方阅读我的评论了吗?我建议使用 CopyOnWriteArrayList

标签: java multithreading arraylist concurrency paintevent


【解决方案1】:

我猜你的同步有点不对劲:

  • ArrayList 是一个没有同步实现的列表
  • 同步方法意味着一次只有 1 个线程可以访问该方法,但函数内部的变量根本不同步

你想要的是让你的列表临时同步。

你可以这样做:

@Override
public void paintComponent(Graphics g) {
  synchronized(paintList) {
    for(Object gO:paintList) {
      g.drawImage( gO.getObjImage(), gO.getXLoc(), gO.getYLoc(), outer.getTransparent(), null);
    }
  }
}

并且在代码中添加对象你的列表做的有些相同。

从这里编辑:

如果您想消除添加线程和绘制线程之间的所有并发问题,您可以这样做:

在添加图片的方法中:

public synchronized void addImage(...) {
  Something newImage = .....
  List<Something> newPaintList = new ArrayList<>(paintList.size() + 1);
  newPaintList.addAll(paintList);
  newPaintList.add(newImage);
  paintList = newPaintList;
}

并且在paint方法中,去掉同步部分。

@Override
public void paintComponent(Graphics g) {
  for(Object gO:paintList) {
    g.drawImage( gO.getObjImage(), gO.getXLoc(), gO.getYLoc(), outer.getTransparent(), null);
  }
}

这样,读取和写入之间不会有任何并发​​性,因为在paintList 上完成的唯一操作是读取。

addImage应该同步,避免两个不同的Thread同时添加图片,这样会导致一个addImage被忽略。

【讨论】:

  • 问题在于同步,当用户在列表中生成更多项目时,绘制线程似乎正在等待。
  • 这个 add 方法与 CopyOnWriteArrayList 的主体基本相同,每次更改集合时都会创建一个新副本,但感谢具体代码。
猜你喜欢
  • 2013-04-26
  • 1970-01-01
  • 1970-01-01
  • 2011-04-25
  • 1970-01-01
  • 2011-04-13
相关资源
最近更新 更多