【问题标题】:Use of the Synchronized Keyword in JavaJava中同步关键字的使用
【发布时间】:2012-05-25 18:44:12
【问题描述】:

我已经阅读了一些关于同步方法的文章(包括 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 关键字? (代码没有它也可以工作,但我怀疑最好有它?)

【问题讨论】:

  • suspects 是做某事的不好理由,在这种情况下也是不正确的。
  • 你能告诉我为什么它不正确吗?
  • 你能告诉我哪两个线程会同时调用这些方法吗?
  • if(moving[i]) return true; else return false; 应该只是 return moving[i]
  • 确实,这个方法写的很仓促。我有一个处理键盘输入然后调用“播放器”类方法的主框架。所以“Player”中的移动线程和主线程可以同时访问moving[]数组。如果需要,我可以在代码中进行编辑,但我认为没有必要。

标签: java multithreading synchronized


【解决方案1】:

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

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

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

几个“风格问题”:

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

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

如果您决定使用 Lock(在这种情况下与同步不是一个很大的优势),您的 isPressed() 应该如下所示:

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

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

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

【讨论】:

  • 好的。非常感谢。这个答案为我清除了一些东西。首先,显然我并没有发疯,因为您似乎明白我所说的多线程访问它的意思,哈哈。其次,感谢有关原始类型的提示。那个演员让我很困扰,但我不知道如何解决它。第三,感谢关于锁的提示,但是您能帮我理解 Lock 的作用和 synchronized 关键字的作用之间的区别吗?
【解决方案2】:

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。

【讨论】:

  • 你能帮我理解为什么我不应该从两个线程与我的班级互动吗?我查看了那个链接,但它并没有真正帮助我......
  • Swing 不是线程安全的——基本上它不会在任何地方使用同步来保持设计简单和无死锁。因此,当您从单独的线程与 swing 交互时,您需要使用 invokeLater。
  • 好的,谢谢。我想我现在明白了:通过 invokeLater() 将 Runnable 放入要完成的事情队列中?然后他们在一个单独的线程中一次执行一个?这是正确的吗?
  • 是的。我已经用一些代码 sn-ps 更新了答案,以说明它是如何使用的。
  • 优秀;谢谢你。非常有帮助!除了你提到的其他事情之外,代码看起来还可以吗?而且,为什么从事件处理线程调用 dirUpdate() 会消除同步的需要?该线程是否为此提供了某种安全性?
猜你喜欢
  • 2022-12-19
  • 2012-03-01
  • 2021-10-26
  • 1970-01-01
  • 2012-12-09
  • 1970-01-01
  • 2011-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多