Java - 同步锁挂平板电脑(蓝牙LE Android)

时间:2016-10-22 21:56:13

标签: java android multithreading bluetooth

我有一个带有Java插件的Unity3D应用程序我用Android Studio编写,用于从我的Android设备访问我的Raspberry Pi 3上的BluetoothLE服务器,以及用于IOS端的XCode的Objective-C插件CoreBluetooth。在插件方面,我的Objective-C代码在IOS上完美运行,但我的Java在Android上崩溃和烧毁,并冻结了整个平板电脑,我并不感到惊讶。另外值得注意的是,到目前为止,我已经花了大约3倍的时间来编写Java中的插件支持。也许现在是时候让一双新眼睛看着它了。

不同之处在于,IOS上的CoreBluetooth功能似乎同步读取GATT特性,而Java需要异步回调,这似乎对我不起作用。它会导致Unity和整个平板电脑挂起,直到重新启动。

这是导致问题的部分(它是两部分)。

第一部分是我让Unity告诉BluetoothLE开始读取引脚(GetPins)的地方。我希望它基本上同步行动并等待它检索数据,这样它就可以返回一个字符串,就像我在IOS版本中编码一样,所以我让它进行同步锁定。

基本上Java正在强加一个在IOS端不必要的异步编码模型(事实上,我不太确定在IOS CoreBluetooth上编写GATT特征读取的异步方式,但我可能是错的),这会导致我必须从Unity重写我的整个插件结构,以支持Java正在做的事情,最终将会更加繁琐,并可能在此过程中破坏其他内容。

    public String GetPins() {
    String returnValue = "";
    if (mConnected) {
        try {
            if (getPinsCharacteristic == null) {
                List<BluetoothGattService> services = mGatt.getServices();
                if (mServiceRead == null) {
                    mGatt.discoverServices();
                    mServiceWrite = mGatt.getService(ServiceWriteUUID);
                    mServiceRead = mGatt.getService(ServiceReadUUID);
                    mServiceNotify = mGatt.getService(ServiceNotifyUUID);
                }
                getPinsCharacteristic = mServiceRead.getCharacteristic(PinsCharacteristic);
            }
        } catch(Exception ex) {
            Log.i("MERRRRRP",String.format("Exception: (%s)",ex.toString()));
        }
        Log.i("MERRRRRP", "Reading Pins");
        if (getPinsCharacteristic != null) {
            try {
                boolean success = mGatt.readCharacteristic(getPinsCharacteristic);
                isReadingPins = true;
                while(isReadingPins) {
                    synchronized (readLockPins) {
                        try {
                            readLockPins.wait(1);
                        }
                        catch(InterruptedException ex) {
                            ex.printStackTrace();
                        }
                    }
                }
                returnValue = new String(getPinsCharacteristic.getValue(), "UTF-8");
            }
            catch(Exception ex) {
                returnValue = ex.toString();
            }
        }
    }
    return returnValue;
}

现在这是onCharacteristicRead的一部分,它实际上得到了结果,并且应该解锁同步锁,我应该重新获得值。相反,整个平板电脑都在冻结。

@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicRead(gatt, characteristic, status);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        if (characteristic.equals(getPinsCharacteristic)) {
            synchronized (readLockPins) {
                isReadingPins = false;
            }
        }
    }
}

1 个答案:

答案 0 :(得分:0)

你的第一个代码块在循环中获取锁定并保持一秒钟。您假设您的第二个代码块将执行并获取第一个块中这些迭代之间的锁定。

这个假设是错误的。锁是不公平的。如果线程A正在获取并释放循环中的锁,并且线程B正试图获取相同的锁,则无法保证线程B在合理的时间内(或者可能永远不会)获得它。

第二个潜在的问题(我无法从您刚发布的代码中确定)是isReadingPins中的变量while(isReadingPins)必须是易变的。

未标记为volatile的变量不会触发内存屏障操作,这会导致在另一个线程中无法在一个线程中写入变量的情况。

在你的情况下,第二个代码块中的isReadingPins = false;可能对运行第一个代码块的线程不可见,因为变量的值将在运行该代码块的CPU内核的本地缓存中,这将是始终为true,因为对false的写入对于另一个CPU核心是本地的,并且由于没有内存屏障,CPU核心高速缓存将不会同步。这就是变量必须是易变的原因。

最后,这是非常笨拙的做法。我不了解Android API,但在常规Java中,这将使用promise / future或至少一个简单的Latch来解决。

编辑另外一件事......如果您的读取函数失败,或者返回的内容不是GATT_SUCCESS,那么isReadingPins永远不会设置为false,您的应用程序就会冻结。这是糟糕的设计。