ReentrantLock在同一线程中被神秘地解锁,即使没有其他线程正在访问它

时间:2019-03-20 11:43:01

标签: java android multithreading synchronization reentrantlock

我有一些我认为非常简单的代码:

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 ReentrantLock,但非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#writenative_write_byte(方法的名称有所不同),writeToTrackAudioTrack->write。但是,他们根本没有帮助我阐明此错误。

3 个答案:

答案 0 :(得分:0)

实际上,您的代码可能会在内部调用时释放Lock / lock.unlock() track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);

当您尝试在finally块中第二次释放锁/ lock.unlock()时,它对当前线程没有任何锁,因此根据您的代码逻辑它抛出错误。

如果要释放lock.unlock()中的Lock / track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);

,请检入代码。

如果您无法删除lock.unlock()内的track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);

然后您执行“双重锁定” /两次获取锁定,然后释放或解锁两次,您的问题将得到解决...

更好地理解示例代码:

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();
      }
   }
}

答案 1 :(得分:0)

  1. 如果您能够删除-内的lock.unlock()

    然后您的当前代码可以正常运行,而无需进行任何修改。

(OR)

  1. 如果您无法删除track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);内的lock.unlock()

    当前代码需要修改为两次获取锁

    track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING);
lock.lockInterruptibly();
lock.lockInterruptibly();

答案 2 :(得分:0)

这是怎么回事:

  1. 线程(A)获得lock并开始工作。
  2. 另一个线程(B)试图获取lock并被阻塞,因为A尚未在unlock()上调用lock
  3. B被另一个线程(C)中断。 C不必知道变量lock
  4. B的阻止被终止,因为它试图可中断地获取锁
  5. 因此finally块由B执行。B没有获取lock,因为当中断发生时A没有释放它。因此,lock.isHeldByCurrentThread()对B是false

不是假设if (!lock.isHeldByCurrentThread())块中的finally是错误的,也不是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();
        }
    }
}