在Java中使用同步关键字

时间:2012-05-25 18:44:13

标签: java multithreading synchronized

我已经阅读过有关同步方法的一些文章(包括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关键字? (代码在没有它的情况下工作,但我怀疑拥有它更好吗?)

2 个答案:

答案 0 :(得分:1)

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

但是,我认为您的代码中存在不同的线程问题。您不应该从单独的线程与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。

答案 1 :(得分:1)

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

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

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

一些“风格问题”:

  1. 不要使用'raw types' - 更喜欢Map&lt; Integer,Integer&gt;映射(也可以保存你从setXxxx()方法中未经检查的-ugly-cast)

  2. 避免单行if - 使代码难以理解:)

  3. 如果你决定使用Lock(在这种情况下不是一个很大的优势v。同步)你的isPressed()应该是这样的:

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

    你必须通过调用Lock.acquire()和release()

    将setX'方法中的赋值“包装”到move []中