1

I am involved in development of Xlet using Java 1.4 API.

The docs say Xlet interface methods (those are actually xlet life-cycle methods) are called on its special thread (not the EDT thread). I checked by logging - this is true. This is a bit surprising for me, because it is different from BB/Android frameworks where life-cycle methods are called on the EDT, but it's OK so far.

In the project code I see the app extensively uses Display.getInstance().callSerially(Runnable task) calls (this is an LWUIT way of running a Runnable on the EDT thread).

So basically some pieces of code inside of the Xlet implementation class do create/update/read operations on xlet internal state objects from EDT thread and some other pieces of code do from the life-cycle thread without any synchronization (including that state variables are not declared as volatile). Smth like this:

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
            }
        });
    }

    ..
}

My question is: does this create a background for rare concurrency issues? Should the access to the state be synchronized explicitly (or at least state should be declared as volatile)?

My guess is it depends on whether the code is run on a multy-core CPU or not, because I'm aware that on a multy-core CPU if 2 threads are running on its own core, then variables are cached so each thread has its own version of the state unless explicitly synchronized.

I would like to get some trustful response on my concerns.

4

1 回答 1

2

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

您需要注意两个问题:

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

例如,采取以下代码:

class Example {
    private int i;

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

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

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

第二个问题是一致的对象状态。以HashMap您的代码中的 为例,在内部它由几个非最终成员变量size,table和. 形成链表的数组在哪里。当一个元素被放入地图或从地图中移除时,需要以原子方式更新这些状态变量中的两个或多个,以使状态保持一致。因为这必须在一个块或类似的块内完成,它才能成为原子的。thresholdmodCounttableEntryHashMapsynchronized

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

想象一下,如果您的 EDT 线程在发生抢占式线程切换时正在调用“get”方法,并且您收到一个试图将另一个条目插入映射的回调,会发生什么情况。但是这次地图超过了负载因子,导致地图调整大小并且所有条目都被重新散列并插入。

于 2012-11-05T15:42:04.623 回答