在本地最终变量上使用synchronized

时间:2016-07-29 08:49:18

标签: java android multithreading synchronization

我正在处理一些问题,我有一个接口实现(GoogleMaps磁贴提供程序),其中该方法需要立即(不是回调)的数据,但是我要获取我必须调用的数据一种在回调中返回数据的方法。我必须将这两个链接在一起,我有一些东西(我认为有效,我还没有测试它),但我担心我得到的一些Android Studio警告。

这是我写的代码:

@Override
public Tile getTile(int x, int y, int zoom) {
    // If the tile provider is not available, return null
    if(tileProvider == null) {
        return NO_TILE;
    }

    // Define the tile request
    final TileRequest tileRequest = new TileRequest(tileProvider.getLayer().getTileWidth(), tileProvider.getLayer().getTileHeight());

    // Make the call to get the tile data, which, depending on the situation, can possibly
    // be handled by another thread.
    tileProvider.getTile(x, y, zoom, new GetDataListener<byte[]>() {
        @Override
        public void onGetData(byte[] data, Exception exception) {
            synchronized (tileRequest) {
                tileRequest.data = data;
                tileRequest.exceptionOccurred = exception != null;
                tileRequest.finishedRequest = true;
                tileRequest.notify();
            }
        }
    });

    synchronized (tileRequest) {
        // If, before this statement was reached, the request has already been finished, call and return getTile immediately.
        if(tileRequest.finishedRequest) {
            return tileRequest.getTile();
        } else {
            try {
                // Wait for the tileRequest to be finished
                tileRequest.wait();

                // Once it is finished (in the callback a notify is called as soon as its finished, so thats how this code is reached)
                // the tile data is available, return the tile
                return tileRequest.getTile();

            } catch(InterruptedException ex) {
                // Exception occurred, return null so GoogleMaps will try it again later
                logger.error("Exception in getTile method: {}", ex.getLocalizedMessage());
                ex.printStackTrace();
                return null;
            }
        }
    }
}

Android Studio在第二行synchronized (tileRequest) {行上给出的是以下警告:

Synchronization on local variable 'tileRequest'

Reports synchronization on a local variable or parameter. Such synchronization has little effect, since different threads usually will have different values for the local variable or parameter. The intent of the code will usually be clearer if synchronization on a field is used.

我对同步,等待和通知的理解不太自信,所以有人可以告诉我,如果我的方法有效,无论警告是什么?

修改 有一些问题表明没有涉及多个线程,我稍微更新了注释,但是tileProvider.getTile(...)方法得到了tile,但是不能保证这不会发生在另一个线程上。

4 个答案:

答案 0 :(得分:3)

在某些情况下,JVM可能会忽略对本地对象的同步,因此如果没有详细分析,您就无法指望它具有正确的语义。我建议采用一种更简单的方法:

CountDownLatch done = new CountDownLatch(1);
tileProvider.getTile(x, y, zoom, new GetDataListener<byte[]>() {
    @Override
    public void onGetData(byte[] data, Exception exception) {
        tileRequest.data = data;
        tileRequest.exceptionOccurred = exception != null;
        tileRequest.finishedRequest = true;
        done.countDown();
    }
});

done.await();
return tileRequest.getTile();

请注意,您可能无法在if (tileRequest.finishedRequest) return tileRequest.getTile();之前拥有done.await(),因为您将失去锁存器提供的可见性保证。根据你的代码,似乎在finishRequest设置为true之后,锁存器将立即被解除阻塞。

答案 1 :(得分:1)

当两个线程尝试访问和更新相同的资源时,需要同步,并且您希望在任何给定时间只有单个线程访问该对象。

在您的情况下,tileRequest是方法范围中定义的局部变量。因此每个线程都拥有它自己的对象实例。因此,同步局部变量只会产生锁定对象的开销,而不会产生任何实际的多线程优势。

答案 2 :(得分:1)

你的代码是正确的,尽管很笨拙。即使您使用方法本地对象进行同步,它也会被GetDataListener括起来,因此它可能会被不同的线程共享。

看看CompletebleFuture。它可能使这段代码更具可读性。

答案 3 :(得分:1)

IMO编译器警告过于热心。

不仅如此,但技术上存在错误:Java代码无法在变量上进行同步。您可以对对象执行同步。

当您编写synchronized (x) { ... }时,x是一个表达式,它会被评估以产生对象引用。大多数程序员希望x成为private final实例变量或private final static变量---这些很容易理解 - 但它不一定是这样的。 x可以是局部变量,也可以是返回对象引用的方法调用。

重要的是,对象是在线程之间共享的。除非某些其他线程也在相同的对象上同步,否则在对象上进行同步没有任何意义。

您获得的警告信息旨在防止常见的新手错误。即使它在技术上是错误的,它也可能会阻止从困惑的新手到某个开发人员帮助中心的每日几十或几百个电话。

当我从事商业软件工作时,我不必担心决定是否正确&#34;正确&#34;或&#34;错误&#34;是否会增加或减少拨打我们支持中心的电话数量。