关于android:同时访问Java中同一对象的不同成员

Concurrently accessing different members of the same object in Java

我熟悉Java中并发并发的许多机制和习语。我所混淆的是一个简单的概念:同一对象的不同成员的并发访问。

我有一组变量可以通过两个线程访问,在本例中是关于游戏引擎中的图形信息。我需要能够修改一个对象在一个线程中的位置,并在另一个线程中读取它。解决这个问题的标准方法是编写以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private int xpos;
private object xposAccess;

public int getXpos() {
    int result;
    synchronized (xposAccess) {
        result = xpos;
    }
    return result;
}

public void setXpos(int xpos) {
    synchronized (xposAccess) {
        this.xpos = xpos;
    }
}

但是,我正在编写一个实时的游戏引擎,而不是一个20个问题的应用程序。我需要快速工作,尤其是当我像处理图形资产的位置那样频繁地访问和修改它们时。我想去掉同步开销。更好的是,我希望完全消除函数调用开销。

1
2
3
4
5
6
7
8
9
private int xpos;
private int bufxpos;
...

public void finalize()
{
    bufxpos = xpos;
    ...
}

使用锁,我可以让线程彼此等待,然后在既不访问也不修改对象的情况下调用Finalize()。在这个快速缓冲步骤之后,两个线程都可以自由地对对象进行操作,一个线程修改/访问xpos,另一个线程访问bufxpos。

我已经成功地使用了类似的方法,将信息复制到第二个对象中,并且每个线程都在一个单独的对象上运行。但是,在上面的代码中,两个成员仍然是同一对象的一部分,当两个线程同时访问对象时,甚至当对不同的成员执行操作时,也会发生一些有趣的事情。不可预测的行为、虚幻的图形对象、屏幕位置的随机错误等。为了验证这确实是一个并发问题,我在一个线程中为两个线程运行了代码,在这个线程中它可以完美地执行。

我最需要的是性能,我正在考虑将关键数据缓冲到单独的对象中。我的错误是由同一对象的并发访问引起的吗?有没有更好的并发解决方案?

编辑:如果你怀疑我对业绩的评价,我应该给你更多的背景。我的引擎是为Android编写的,我使用它来绘制成百上千的图形资产。我有一个单线程解决方案可以工作,但是自从实现多线程解决方案以来,我看到性能几乎翻了一番,尽管存在幻影并发问题和偶尔出现的未捕获异常。

编辑:感谢您对多线程性能的精彩讨论。最后,我能够通过在工作线程处于休眠状态时缓冲数据来解决这个问题,然后允许它们在对象中各自操作自己的数据集。


如果您只处理单个原语,如AtomicInteger,它的操作类似于compareAndSet,那么它是非常好的。它们是非阻塞的,您可以获得大量原子性,并在需要时返回到阻塞锁。

对于原子性地设置访问变量或对象,可以利用非阻塞锁,返回到传统锁。

但是,从代码中的位置向前迈出的最简单的一步是使用synchronized,但不是使用隐式this对象,而是使用几个不同的成员对象,每个需要原子访问的成员分区一个:synchronized(partition_2) { /* ... */ }synchronized(partition_1) { /* ... */ }等,其中有private Object partition1;private Object partition2;等成员。

但是,如果无法对成员进行分区,则每个操作必须获取多个锁。如果是这样,请使用前面链接的Lock对象,但请确保所有操作都以某种通用顺序获取所需的锁,否则您的代码可能会死锁。

更新:如果volatile对性能造成了不可接受的影响,那么可能无法提高性能。基本的基础方面,你不能解决,是互斥必然意味着一个折衷与内存层次结构的实质利益,即缓存。每处理器最快的核心内存缓存无法保存正在同步的变量。处理器寄存器可以说是最快的"缓存",即使处理器足够复杂以保持最近的缓存一致,它仍然排除在寄存器中保留值。希望这能帮助你看到它是一个基本的性能块,没有魔杖。

在移动平台的情况下,出于电池寿命的考虑,该平台专门针对让任意应用程序尽可能快地运行而设计。让任何一个应用程序在几个小时内耗尽电池并不是一个优先事项。

考虑到第一个因素,最好的办法就是重新设计你的应用程序,这样它就不需要太多的互斥——考虑不一致地跟踪x-pos,除非两个物体靠近,比如说在一个10x10盒子里。因此,您锁定了一个10x10框的粗网格,只要其中有一个对象,就可以不一致地跟踪位置。不确定这是否适用于您的应用程序,也不一定对您的应用程序有意义,但它只是一个示例,用来传达算法重新设计的精神,而不是寻找更快的同步方法。


我不明白你的意思,但一般来说

Is there a better solution for concurrency?

是的,有:

  • 相对于内部内置的锁更喜欢Java锁API。
  • 考虑使用原子API中提供的非阻塞结构(如atomicinteger)以获得更好的性能。


我认为使用不可变对象进行线程间通信可以避免同步或任何类型的锁定。假设要发送的消息如下所示:

1
2
3
4
5
6
7
8
public final class ImmutableMessage {
    private final int xPos;
    // ... other fields with adhering the rules of immutability

    public ImmutableObject(int xPos /* arguments */) { ... }

    public int getXPos() { return xPos; }
}

然后在编写器线程中的某个地方:

1
sharedObject.message = new ImmutableMessage(1);

读卡器线程:

1
2
ImmutableMessage message = sharedObject.message;
int xPos = message.getXPos();

共享对象(用于简化的公共字段):

1
2
3
4
public class SharedObject {

    public volatile ImmutableMessage message;
}

我想,在一个实时游戏引擎中,情况会迅速变化,最终可能会产生大量的ImmutableMessage对象,这最终可能会降低性能,但可能会被该解决方案的非锁定性质所平衡。

最后,如果你有一个小时来讨论这个话题,我认为值得看看Angelika Langer的Java内存模型。