3

我已经阅读了一些关于同步方法的文章(包括 Oracle 的),但我不确定我是否理解正确。

我有以下代码:

public class Player extends javax.swing.JLabel implements Runnable{
    private static int off2[] = {-1, 0, 1, 0}, off1[] = {0, 1, 0, -1};
    private static final Map dir = new java.util.HashMap<Integer, Integer>();
    static{
        dir.put(KeyEvent.VK_UP, 0);
        dir.put(KeyEvent.VK_RIGHT, 1);
        dir.put(KeyEvent.VK_DOWN, 2);
        dir.put(KeyEvent.VK_LEFT, 3);
    }
    private boolean moving[] = new boolean[4];

    @Override
    public void run(){
        while(true){
            for(Object i : dir.values()){
                if(isPressed((Integer)i)) setLocation(getX() + off1[(Integer)i], getY() + off2[(Integer)i]);
            }
            try{
                Thread.sleep(10);
            }catch(java.lang.InterruptedException e){
                System.err.println("Interrupted Exception: " + e.getMessage());
            }
        }
    }
    public void start(){
        (new Thread(this)).start();
    }

    private synchronized boolean isPressed(Integer i){
        if(moving[i]) return true;
        else return false;
    }

    public synchronized void setPressed(KeyEvent evt) {
        if(dir.containsKey(evt.getKeyCode()))moving[(Integer)dir.get(evt.getKeyCode())] = true;
    }
    public synchronized void setReleased(KeyEvent evt){
        if(dir.containsKey(evt.getKeyCode()))moving[(Integer)dir.get(evt.getKeyCode())] = false;
    }
}

现在它所做的只是运动。我的理解是当我的主窗体的键侦听器注册 keyPressed 和 Released 事件时,从主线程调用 setPressed 和 setReleased。这段代码合理吗?是否正确使用了 synchronized 关键字?(代码没有它也能工作,但我怀疑最好有它?)

4

2 回答 2

1

使用同步是正确的,但我认为没有必要,因为在当前的实现中,并行访问数组不会导致不一致。

但是,我认为您的代码中存在不同的线程问题。您不应该从单独的线程与 Swing (=call setLocation()) 交互,请参阅http://docs.oracle.com/javase/1.4.2/docs/api/javax/swing/SwingUtilities.html#invokeLater( java.lang.Runnable)。所以你需要将更新代码包装到一个 Runnable 中:

private boolean moving[] = new boolean[4];
Runnable dirUpdate = new Runnable() {
  for(Object i : dir.values()){
    if(isPressed((Integer)i)) setLocation(getX() + off1[(Integer)i], getY() + off2[(Integer)i]);
  }
};

来自线程的调用将如下所示:

SwingUtils.invokeLater(dirUpdate);

请注意,在这种情况下,您将不再需要同步,因为 dirUpdate 将从事件处理线程中调用。

您可能希望在 isPressed() 中检查 null 以避免未映射键上的异常。

运动的更简单表示可能是具有 dx 和 dy 变量,这些变量将由关键事件设置为 -1、1 或 0。

于 2012-05-25T19:00:04.167 回答
1

不确定事物的 Swing 方面,但一般来说,您需要同步来保护可能由多个线程访问的那些共享数据(在您的情况下是移动 [])。

在这种情况下,您需要同步,因为 move[] 可以通过 setXxx 方法(主线程)中的任一方法(主线程)写入或您启动的线程读取时访问。

从 Java 5 开始,java.concurrent 包中的功能要好得多,我建议您考虑使用 Lock 来保护 Moving[] 或 AtomicBoolean。

几个“风格问题”:

  1. 不要使用“原始类型”——更喜欢 Map<Integer, Integer> 到 Map (这也使您免于 setXxxx() 方法中未经检查的 -ugly- 强制转换)

  2. 避免单行 if's - 使您的代码难以辨认:)

如果您决定使用 Lock (在这种情况下不是一个很大的优势 v. synchronized )您的 isPressed() 应该看起来像:

// ...
private Lock movingLock = new ReentrantLock();

private  boolean isPressed(Integer i){
  try {
    movingLock.acquire();
    return moving[i];
  } finally
    movingLock.release();
  }        
}

您必须通过调用 Lock.acquire() 和 release() 在 setXxx 方法中“包装”对 move[] 的分配

于 2012-05-25T19:08:27.890 回答