volatile是一种在线程之间传递MotionEvent的安全方法吗?

时间:2010-06-29 01:33:17

标签: java android multithreading

我很好奇这个方法的安全性,我考虑在我的Android应用程序中传递触摸事件(并测试我对Java中的并发性的理解)。这是基础知识:

我有一个SurfaceView连接到SurfaceHolder.Callback以获取用户输入事件,主要是onTouchEvent回调。调用onTouchEvent方法后,我会看到event.getAction() == MotionEvent.ACTION_UP是否会调用我命名为postTouchEvent的方法,这是我的应用程序线程的成员方法,用于更新应用程序的状态和画到画布上。

SurfaceView

@Override
public boolean onTouchEvent(final MotionEvent event)
{
    mAppThread.postTouchEvent(event);
}

AppThread

private volatile MotionEvent mInputEvent;

public void postTouchEvent(final MotionEvent event)
{
    this.mInputEvent = event;
}

@Override
public void run()
{
    // while loop ...

    if (this.mInputEvent != null)
    {
        final MotionEvent inputEvent = this.mInputEvent;
        this.mInputEvent == null;

        // update state based on inputEvent (not this.mInputEvent)
    }

    // draw to canvas
}

现在我明白它肯定不是原子的,但是因为我从框架中接收它后将其视为不可变的,这不会起作用吗? (而不是同步post方法和if语句,我没有问题,但我要学习。)

以下是我的想法。我知道我将对该对象有一个有效的引用,但我不确定我将实际看到该对象的状态。虽然测试一切都运行得很好,但我知道线程异常可能是多么罕见,即使某些东西被破坏了。

另外,我可以看到一个问题:如果另一个MotionEvent出现,run()方法中的inputEvent可能会设置为与this.mInputEvent != null时引用的事件不同的事件。已经过检查,但这确实不是问题。

那么,有什么我遗失的,或者就我的目的而言,这应该没问题吗?

2 个答案:

答案 0 :(得分:16)

不,这不安全,但不是因为你可能期望的原因。

ViewRoot.java, line 1841。这是在视图层次结构中调度MotionEvent的代码。第1841行是finally块的一部分,该块在刚刚调度的MotionEvent上调用recycle()

MotionEvents不像大多数对象那样被垃圾收集,它们被合并和回收,以避免在事件发送期间不必要的内存分配和垃圾收集。回收MotionEvent会将其返回到对象池,以便稍后在需要新的MotionEvent时再次使用。调用recycle()后,应将MotionEvent视为无效。

您的示例代码可能最终会读取已被框架重用的MotionEvent对象,现在包含完全不同的数据。

如果您计划在onTouchEvent返回后挂起到MotionEvent,请使用MotionEvent.obtain(event)克隆它。静态obtain()方法将从具有相同内容的对象池返回新的MotionEvent。

当你完成了recycle()自己{1}}自己将MotionTvent返回池中时,你应该调用obtain()。如果你忘记了这一步,这将不是一个大问题,它将成为常规Java垃圾,框架将在需要时创建新的MotionEvent。但是,系统可以非常快速地调度MotionEvents,以便与此优化协作可以在某些设备上产生非凡的性能差异。

答案 1 :(得分:1)

我认为这是“安全的”,模仿你已经确定丢失事件的点。

对于易失性和后续读取操作的写入操作存在“发生在之前”关系。这与单个线程中的操作中固有的“事先发生”关系相结合,应该意味着您的应用程序线程将在其引用写入volatile时查看事件对象的真实状态。

当然,如果事件侦听器线程在更新引用后更改了事件对象的状态,则无法保证应用程序线程将看到它们。同上,如果第三个线程更新事件对象。