【问题标题】:ReentrantLock gets mysteriously unlocked within the same thread, even while no other threads are accessing itReentrantLock 在同一个线程中被神秘地解锁,即使没有其他线程正在访问它
【发布时间】: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();
    }
}

lockfair 可重入锁,但非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。但是,他们根本没有帮助我阐明这个错误。

【问题讨论】:

  • 有趣的错误。我建议尝试更多地了解锁认为它属于哪个线程 - 使用调试器或通过子类化ReentrantLock 以公开getOwner()。然后我会尝试使用条件断点来捕捉状态变化的时刻。
  • 我曾经尝试过,似乎锁只是被完全解锁,而不是由特定线程拥有(getOwner 返回null)。我也尝试了断点,但我无法在 JVM 代码中设置语句断点,并且使用方法断点将性能降低到完全无法使用的程度。
  • 你应该将lock设为私有。如果您有理由不将其设为私有,那么这可能就是问题所在。
  • @meew0 好吧,如果你想进一步探究这个问题,你可以复制 ReentrantLock 的源代码——称之为 ReentrantLock2,然后使用那个类。您可以根据需要设置断点,并添加自定义调试代码。甚至可能导致行为改变。
  • @MattTimmermans 我明白你的意思,但即使我删除 all 对锁的其他引用,问题仍然存在,所以这不是问题。

标签: java android multithreading synchronization reentrantlock


【解决方案1】:
  1. 如果您能够在track.write(data, offset, size, AudioTrack.WRITE_NON_BLOCKING); 中删除lock.unlock()

    那么您当前的代码将可以正常工作,无需进行任何修改。

(或)

  1. 如果您无法在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】:

    会发生什么:

    1. 线程 (A) 获取 lock 并开始工作。
    2. 另一个线程 (B) 尝试获取 lock 并被阻止,因为 A 尚未在 lock 上调用 unlock()
    3. B 被另一个线程 (C) 中断。 C 不必知道变量lock
    4. B 的阻塞被终止,因为它试图中断地获取锁。
    5. 因此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();
            }
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-31
        • 2015-09-04
        • 2023-03-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多