Xlet开发中可能存在的并发问题 [英] Possible concurrency issue in Xlet development

查看:113
本文介绍了Xlet开发中可能存在的并发问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我参与使用Java 1.4 API开发 Xlet



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



项目代码我看到应用程序广泛使用 Display.getInstance()。callSerially(Runnable任务)调用(这是一种LWUIT方式运行 Runnable



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

 类MyXlet实现Xlet {

Map state = new HashMap

public void initXlet(XletContext context)throws XletStateChangeException {
state.put(foo,bar); //不在EDT线程上运行

Display.getInstance()。callSerially(new Runnable(){
public void run(){
//线程
对象foo = state.get(foo);
//分支逻辑取决于foo
}
});
}

..
}



问题是:这是否为罕见的并发问题创建了一个背景?如果对状态的访问被明确同步(或者至少状态应该被声明为volatile)?



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


解决方案

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



有两个问题需要注意:



第一个问题,可见性(你已经提到过)仍然可以在单处理器上发生。问题是,允许JIT编译器在寄存器中高速缓存可变寄存器,并且在上下文切换上,OS将最有可能将寄存器的内容转储到线程上下文,以便稍后可以恢复它。然而,这不同于将寄存器的内容写回到对象的字段,因此在上下文切换之后,我们不能假定对象的字段是最新的。



例如,取以下代码:

  class Example {
private int i;

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

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



所以,让我们说一个线程正在执行上面的循环,然后得到它的pre-empted。新调度的线程将不能看到 i 的最新值,因为 i 的最新值为寄存器和该寄存器被保存到线程本地执行上下文。至少需要将实例字段 i 声明为 volatile ,以强制执行



第二个问题是一致的对象状态。以你的代码中的 HashMap 为例,它由几个非最终成员变量组成 size 阈值 modCount 。其中 table 是形成链表的 Entry 的数组。当元素被放入或从映射中移除时,这些状态变量中的两个或多个需要被原子地更新以使状态一致。对于 HashMap 这必须在同步块或类似的内部完成,因为它是原子的。



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



想象一下,如果您的EDT线程在发生抢先线程切换时调用get方法时会发生什么并且您会收到一个尝试在地图中插入另一个条目的回调。但是这次地图超过负载因子导致地图调整大小,所有的条目被重新哈希和插入。


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.

解决方案

Yes, in the scenario you describe, the access to the shared state must be made thread safe.

There are 2 problems that you need to be aware of:

The first issue, visability (which you've already mentioned), can still occur on a uniprocessor. The problem is that the JIT compiler is allowed to cache varibles in registers and on a context switch the OS will most likely dump the contents of the registers to a thread context so that it can be resumed later on. However, this is not the same as writing the contents of the registers back to the fields of an object, hence after a context switch we can not assume that the fields of an object is up to date.

For example, take the follow code:

class Example {
    private int i;

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

Since the loop variable (an instance field) i is not declared as volatile, the JIT is allowed to optimise the loop variable i using a CPU register. If this happens, then the JIT will not be required to write the value of the register back to the instance variable i until after the loop has completed.

So, lets's say a thread is executing the above loop and it then get's pre-empted. The newly scheduled thread won't be able to see the latest value of i because the latest value of i is in a register and that register was saved to a thread local execution context. At a minimum the instance field i will need to be declared volatile to force each update of i to be made visible to other threads.

The second issue is consistent object state. Take the HashMap in your code as an example, internally it is composed of several non final member variables size, table, threshold and modCount. Where table is an array of Entry that forms a linked list. When a element is put into or removed from the map, two or more of these state variables need to be updated atomically for the state to be consistent. For HashMap this has to be done within a synchronized block or similar for it to be atomic.

For the second issue, you would still experience problems when running on a uniprocessor. This is because the OS or JVM could pre-emptively switch threads while the current thread is part way through executing the put or remove method and then switch to another thread that tries to perform some other operation on the same HashMap.

Imagine what would happen if your EDT thread was in the middle of calling the 'get' method when a pre-emptive thread switch occurs and you get a callback that tries to insert another entry into the map. But this time the map exceeds the load factor causing the map to resized and all the entries to be re-hashed and inserted.

这篇关于Xlet开发中可能存在的并发问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆