ReentrantLock 在同一个线程中被神秘地解锁,即使没有其他线程正在访问它
Posted
技术标签:
【中文标题】ReentrantLock 在同一个线程中被神秘地解锁,即使没有其他线程正在访问它【英文标题】:ReentrantLock gets mysteriously unlocked within the same thread, even while no other threads are accessing it 【发布时间】: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 对锁的其他引用,问题仍然存在,所以这不是问题。
【参考方案1】:
如果您能够在track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
中删除lock.unlock()
那么您当前的代码将可以正常工作,无需进行任何修改。
(或)
如果您无法在track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
中删除lock.unlock()
当前代码需要使用两次获取锁进行调整:
lock.lockInterruptibly();
lock.lockInterruptibly();
public int internalWrite(byte[] data, int offset, int size) throws InterruptedException
lock.lockInterruptibly();
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();
【讨论】:
【参考方案2】:会发生什么:
-
线程 (A) 获取
lock
并开始工作。
另一个线程 (B) 尝试获取 lock
并被阻止,因为 A 尚未在 lock
上调用 unlock()
。
B 被另一个线程 (C) 中断。 C 不必知道变量lock
。
B 的阻塞被终止,因为它试图中断地获取锁。
因此finally
-block 由B 执行。B 没有获取lock
,因为A 没有在中断发生时释放它。因此,lock.isHeldByCurrentThread()
是 B 的 false
。
不要假设在finally
块中的if (!lock.isHeldByCurrentThread())
的情况下是错误的或B 的不需要的状态,您可以简单地继续而不记录错误/警告,也可以不unlock()
lock
在这种情况下。
public int internalWrite(byte[] data, int offset, int size) throws InterruptedException
try
lock.lockInterruptibly(); // if outside the try block the next lines would be executed in the described situation without having acquired the lock
if (state == State.RELEASED) throw new TrackReleasedException();
return track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
finally
if (lock.isHeldByCurrentThread())
lock.unlock();
【讨论】:
【参考方案3】:实际上,您的代码可能会在内部调用时释放 Lock / lock.unlock()
track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
当您在 finally 块中第二次尝试释放锁 /lock.unlock()
时,它与当前线程没有任何锁,因此它会根据您的代码逻辑抛出错误。
如果您要在track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
中释放锁定/lock.unlock()
,请检查您的代码
如果您无法删除 track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
中的 lock.unlock()
然后您执行“DOUBLE LOCK”/获得两次锁定然后释放或解锁两次您的问题将得到解决...
便于理解的示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class Main
private final static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args)
try
System.out.println("Hello World:::::"+internalWrite());
catch(InterruptedException ie)
ie.printStackTrace();
public static int internalWrite() throws InterruptedException
lock.lockInterruptibly();
lock.lockInterruptibly();// Comment this line to reproduce your issue
try
if(lock.isHeldByCurrentThread())
System.out.println("lock::::"+lock.isHeldByCurrentThread());
lock.unlock();
System.out.println("lock::::"+lock.isHeldByCurrentThread());
return 1;
finally
if (!lock.isHeldByCurrentThread())
System.out.println("phonographnative internalWrite() lock is not held by current thread! " + Thread.currentThread());
else lock.unlock();
【讨论】:
以上是关于ReentrantLock 在同一个线程中被神秘地解锁,即使没有其他线程正在访问它的主要内容,如果未能解决你的问题,请参考以下文章