Xlet开发中可能出现的并发问题

我参与了使用Java 1.4 API开发Xlet 。

文档说Xlet接口方法(实际上是xlet生命周期方法)是在其特殊线程(而不是EDT线程)上调用的。 我通过记录检查 – 这是真的。 这对我来说有点令人惊讶,因为它与在EDT上调用生命周期方法的BB / Android框架不同,但到目前为止它还可以。

在项目代码中,我看到应用程序广泛使用Display.getInstance().callSerially(Runnable task)调用(这是在EDT线程上运行Runnable的LWUIT方式)。

所以基本上Xlet实现类中的一些代码片段对来自EDT线程的xlet内部状态对象进行创建/更新/读取操作,而其他一些代码片段来自生命周期线程而没有任何同步(包括那些状态变量不是声明为volatile)。 Smth是这样的:

 class MyXlet implements Xlet { Map state = new HashMap(); public void initXlet(XletContext context) throws XletStateChangeException { state.put("foo", "bar"); // does not run on the EDT thread Display.getInstance().callSerially(new Runnable() { public void run() { // runs on the EDT thread Object foo = state.get("foo"); // branch logic depending on the got foo } }); } .. } 

我的问题是:这是否为罕见的并发问题创建了背景? 是否应明确同步对状态的访问(或至少应将状态声明为volatile)?

我的猜测是它取决于代码是否在多核CPU上运行,因为我知道在多核CPU上,如果2个线程在其自己的核心上运行,则变量被缓存,因此每个线程都有除非明确同步,否则它自己的状态版本。

我想对我的担忧得到一些可信的回应。

是的,在您描述的场景中,必须使对共享状态的访问成为线程安全的。

您需要注意两个问题:

第一个问题,可见性(您已经提到过),仍然可以在单处理器上进行。 问题是允许JIT编译器在寄存器中缓存varible,在上下文切换中,OS很可能将寄存器的内容转储到线程上下文,以便稍后可以恢复。 但是,这与将寄存器的内容写回对象的字段不同,因此在上下文切换之后,我们不能假设对象的字段是最新的。

例如,请使用以下代码:

 class Example { private int i; public void doSomething() { for (i = 0; i < 1000000; i ++) { doSomeOperation(i); } } } 

由于循环变量(实例字段) i未声明为volatile,因此允许JIT使用CPU寄存器优化循环变量i 。 如果发生这种情况,那么在循环完成之前,JIT将不需要将寄存器的值写回实例变量i

所以,让我们假设一个线程正在执行上面的循环,然后它被抢先一步。 新调度的线程将无法看到i的最新值,因为i的最新值位于寄存器中,并且该寄存器已保存到线程本地执行上下文中。 实例字段至少需要声明为volatile以强制i每个更新对其他线程可见。

第二个问题是一致的对象状态。 以代码中的HashMap为例,在内部它由几个非最终成员变量sizetablethresholdmodCount 。 其中table是一个Entry数组,形成一个链表。 当元素放入映射或从映射中移除时,需要以primefaces方式更新这些状态变量中的两个或更多个以使状态保持一致。 对于HashMap这必须在synchronized块或类似的块中完成,以使其成为primefaces。

对于第二个问题,在单处理器上运行时仍会遇到问题。 这是因为操作系统或JVM可以抢先切换线程,而当前线程是执行put或remove方法的一部分,然后切换到另一个尝试在同一个HashMap上执行其他操作的线程。

想象一下,如果您的EDT线程在发生先发制人的线程切换时调用'get'方法,并且您得到一个试图在地图中插入另一个条目的回调,会发生什么。 但是这次映射超出了加载因子,导致映射resize并且所有条目都被重新散列和插入。