【发布时间】:2019-08-11 02:24:15
【问题描述】:
我有一些我认为非常简单的代码:
public int internalWrite(byte[] data, int offset, int size) throws InterruptedException {
lock.lockInterruptibly();
try {
if (state == State.RELEASED) throw new TrackReleasedException();
return track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
} finally {
if (!lock.isHeldByCurrentThread()) {
Log.e("phonographnative", "internalWrite() lock is not held by current thread! " + Thread.currentThread());
} else lock.unlock();
}
}
lock 是 fair 可重入锁,但非fair 也会出现此问题。 track 是 Android AudioTrack;它的write 方法主要是本机代码(但与线程无关)。它无论如何都无法访问lock。在实践中实际上从未抛出异常(在调查此行为时也从未抛出异常)。发生的情况是,非常可重复地(稍后会详细介绍),锁将在同一个线程中神秘地解锁,从而导致出现日志消息。以前,当我没有进行此检查时,预计会抛出 IllegalMonitorStateException。在这种情况发生几次之后,在锁的代码中就会出现java.lang.AssertionError: Attempt to repark。一些示例性的日志输出:
2019-03-20 12:20:37.428 8097-8181/com.kabouzeid.gramophone.debug E/phonographnative: internalWrite() lock is not held by current thread! Thread[phonographnative-decoding-65308.0,5,main]
2019-03-20 12:20:37.428 8097-8184/com.kabouzeid.gramophone.debug E/phonographnative: internalWrite() lock is not held by current thread! Thread[phonographnative-decoding-65308.0,5,main]
2019-03-20 12:20:37.428 8097-8181/com.kabouzeid.gramophone.debug E/phonographnative: internalWrite() lock is not held by current thread! Thread[phonographnative-decoding-65308.0,5,main]
2019-03-20 12:20:37.428 8097-8184/com.kabouzeid.gramophone.debug E/phonographnative: internalWrite() lock is not held by current thread! Thread[phonographnative-decoding-65308.0,5,main]
2019-03-20 12:20:37.430 8097-8184/com.kabouzeid.gramophone.debug E/AndroidRuntime: FATAL EXCEPTION: phonographnative-decoding-65308.0
Process: com.kabouzeid.gramophone.debug, PID: 8097
java.lang.AssertionError: Attempt to repark
at java.lang.Thread.parkFor$(Thread.java:2143)
at sun.misc.Unsafe.park(Unsafe.java:325)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:161)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:840)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)
at com.kabouzeid.gramophone.service.ffmpeg.AudioContext.internalWrite(AudioContext.java:197)
at com.kabouzeid.gramophone.service.ffmpeg.AudioContext.write(AudioContext.java:177)
at com.kabouzeid.gramophone.service.ffmpeg.FFmpegPlayer.decodeAndPlayAudio(Native Method)
at com.kabouzeid.gramophone.service.ffmpeg.FFmpegPlayer.lambda$new$0(FFmpegPlayer.java:72)
at com.kabouzeid.gramophone.service.ffmpeg.-$$Lambda$FFmpegPlayer$MKAlsDZBJzprKYoChfgA-0JlIi8.run(lambda)
at java.lang.Thread.run(Thread.java:761)
即使没有其他线程尝试运行此代码,并且即使我注释掉所有其他尝试锁定/解锁此锁(由其他线程在不同的代码部分中),也会发生这种情况。这个问题很少会间歇性地发生,但当我尝试取消暂停正在写入的 AudioTrack 时会可靠地发生。最初写入它或执行其他任何操作(例如从头开始播放)时都不会发生这种情况。这种取消暂停发生在一个完全不同的线程上,我无法确定这两件事之间的因果关系。这可能只是一些随机调度程序的疯狂。
internalWrite 方法被非常频繁地调用,大约每秒数千次。我感觉这只是 Android 的 ReentrantLock 实现中的一个错误,因为被命中的断言完全在 JVM 代码中。 (“尝试重新停车”是什么意思?)但我不能排除我在自己的代码中遗漏了一些其他细节,如果对此有任何想法,我将不胜感激!
完整代码可以在here找到。我还查到了Android代码中的相关部分:AudioTrack#write、native_write_byte(方法叫法不同)、writeToTrack、AudioTrack->write。但是,他们根本没有帮助我阐明这个错误。
【问题讨论】:
-
有趣的错误。我建议尝试更多地了解锁认为它属于哪个线程 - 使用调试器或通过子类化
ReentrantLock以公开getOwner()。然后我会尝试使用条件断点来捕捉状态变化的时刻。 -
我曾经尝试过,似乎锁只是被完全解锁,而不是由特定线程拥有(
getOwner返回null)。我也尝试了断点,但我无法在 JVM 代码中设置语句断点,并且使用方法断点将性能降低到完全无法使用的程度。 -
你应该将
lock设为私有。如果您有理由不将其设为私有,那么这可能就是问题所在。 -
@meew0 好吧,如果你想进一步探究这个问题,你可以复制 ReentrantLock 的源代码——称之为 ReentrantLock2,然后使用那个类。您可以根据需要设置断点,并添加自定义调试代码。甚至可能导致行为改变。
-
@MattTimmermans 我明白你的意思,但即使我删除 all 对锁的其他引用,问题仍然存在,所以这不是问题。
标签: java android multithreading synchronization reentrantlock