有益的CountDownLatch和棘手的Java死锁

您是否曾经使用过java.util.concurrent.CountDownLatch

这是在两个或多个线程之间实现同步的非常方便的类,在该类中,一个或多个线程可以等待,直到在其他线程中执行的一组操作完成为止(请参阅javadoc和此文章 )。 CountDownLatch在适当的情况下可以节省您的时间,您必须了解此类。

与往常一样,如果代码不好,线程同步会引发死锁。 由于并发用例可能非常复杂,因此开发人员必须非常小心。 在这里我不会描述复杂的并发问题,但是如果您不小心使用CountDownLatch ,可能会遇到一个简单的问题。

假设您有2个线程(线程1和线程2)共享一个java.util.concurrent.ArrayBlockingQueue,并且您想使用CountDownLatch对其进行同步。 检查以下简单示例:

package gr.qiozas.simple.threads.countdownlatch;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
 
public class DeadlockCaseCDL {
 
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c = new CountDownLatch(1);
        ArrayBlockingQueue b = new ArrayBlockingQueue(1);
 
        new Thread(new T1(c, b)).start();
        new Thread(new T2(c, b)).start();
    }
 
    private static class T1 implements Runnable {
        private CountDownLatch c;
        private ArrayBlockingQueue b;
        private T1(CountDownLatch c, ArrayBlockingQueue b) {
            this.c = c; this.b = b;
        }
        public void run() {
          try {
            b.put(234);
            b.put(654);
            doWork(T1.class);
            c.countDown();
            doWork(T1.class);
          } catch (InterruptedException ex) {}
       }
    }
 
    private static class T2 implements Runnable {
        private CountDownLatch c;
        private ArrayBlockingQueue b;
        private T2(CountDownLatch c, ArrayBlockingQueue b) {
            this.c = c; this.b = b;
        }
        public void run() {
          try {
            doWork(T2.class);
            c.await();
            doWork(T2.class);
            System.out.println("I just dequeue "+b.take());
            System.out.println("I just dequeue "+b.take());
          } catch (InterruptedException ex) {}
       }
    }
 
    private static void doWork(Class classz) {
        System.out.println(classz.getName()+" do the work");
    }
}

您在上面看到的是,主线程创建了一个计数为1的CountDownLatch。 和一个容量为“ 1”的ArrayBlockingQueue 然后生成“ 2个线程”。 ArrayBlockingQueue将用于实际的“工作”(入队和出队),而CountDownLatch将用于同步线程(入队必须在出队前完成)。

线程1使2条消息入队,线程2希望使它们出队,但仅在线程1使两条消息入队之后。 此同步由CountDownLatch保证。 您认为此代码可以吗? 不,这不是造成死锁的原因!

如果仔细看第10行,您将看到我初始化ArrayBlockingQueue的容量等于“ 1”。 但是线程1要排队2条消息,然后释放(CountDownLatch)的锁,以便随后被线程2占用。 容量“ 1? 队列阻塞线程1,直到另一个线程将一个消息从队列中出队,然后再次尝试使第二条消息入队。 不幸的是,只有线程2使消息从队列中出队,但是由于线程1拥有CountDownLatch锁,因此线程2无法使任何消息出队,因此被阻塞。 因此,由于两个线程都被阻塞(等待获取不同的锁),我们确实有一个死锁 。 线程1等待ArrayBlockingQueue锁定,而线程2等待CountDownLatch锁定(您也可以在下面的相关线程转储中看到它)。

如果我们增加队列的容量,那么此代码将毫无问题地运行,但这不是本文的重点。 您需要了解的是,必须谨慎使用CountDownLatch,以避免此类危险情况。 您必须了解类的功能情况,向团队的其他开发人员详细说明该功能,编写有用的Javadoc,并始终编写在极端情况下(不仅对于开心路径而言)都可靠的代码。

您可能会帮助您的另一点是,现代JVM无法检测到此死锁。 尝试一下。

如您所知,现代JVM(Hotspot和JRockit)都能够检测到简单的死锁,并在线程转储中报告它们。 请参阅从Hotspot JVM检测到的简单死锁示例:

Found one Java-level deadlock:
=============================
"Thread-6":
waiting to lock monitor 0x00a891ec (object 0x06c616e0, a java.lang.String),
which is held by "Thread-9"
"Thread-9":
waiting to lock monitor 0x00a8950c (object 0x06c61708, a java.lang.String),
which is held by "Thread-6"

您可以尝试DeadlockCaseCDL并获得线程转储(在GNU / Linux上仅运行“ kill -3 ‹jvm_pid›”)。 您将看到线程转储看起来很正常,JVM没有检测到死锁,但是您处于死锁状态!!! 因此,请注意,JVM无法检测到这种死锁。

从我的本地执行中检查以下线程转储示例:

Full thread dump Java HotSpot(TM) Server VM (17.1-b03 mixed mode):

"DestroyJavaVM" prio=10 tid=0x0946e800 nid=0x5382 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Thread-1" prio=10 tid=0x094b1400 nid=0x5393 waiting on condition [0x7c79a000]
   java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for   (a java.util.concurrent.CountDownLatch$Sync)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:969)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1281)
 at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:207)
 at gr.qiozas.simple.threads.countdownlatch.DeadlockCaseCDL$T2.run(DeadlockCaseCDL.java:50)
 at java.lang.Thread.run(Thread.java:662)

"Thread-0" prio=10 tid=0x094afc00 nid=0x5392 waiting on condition [0x7c7eb000]
   java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for   (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
 at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
 at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:252)
 at gr.qiozas.simple.threads.countdownlatch.DeadlockCaseCDL$T1.run(DeadlockCaseCDL.java:29)
 at java.lang.Thread.run(Thread.java:662)

"Low Memory Detector" daemon prio=10 tid=0x0947f800 nid=0x5390 runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"CompilerThread1" daemon prio=10 tid=0x7cfa9000 nid=0x538f waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"CompilerThread0" daemon prio=10 tid=0x7cfa7000 nid=0x538e waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x7cfa5800 nid=0x538d waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=10 tid=0x7cf96000 nid=0x538c in Object.wait() [0x7cd15000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on  (a java.lang.ref.ReferenceQueue$Lock)
 at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
 - locked  (a java.lang.ref.ReferenceQueue$Lock)
 at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
 at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=0x7cf94800 nid=0x538b in Object.wait() [0x7cd66000]
   java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on  (a java.lang.ref.Reference$Lock)
 at java.lang.Object.wait(Object.java:485)
 at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
 - locked  (a java.lang.ref.Reference$Lock)

"VM Thread" prio=10 tid=0x7cf92000 nid=0x538a runnable

"GC task thread#0 (ParallelGC)" prio=10 tid=0x09475c00 nid=0x5383 runnable

"GC task thread#1 (ParallelGC)" prio=10 tid=0x09477000 nid=0x5384 runnable

"GC task thread#2 (ParallelGC)" prio=10 tid=0x09478800 nid=0x5385 runnable

"GC task thread#3 (ParallelGC)" prio=10 tid=0x0947a000 nid=0x5387 runnable

"VM Periodic Task Thread" prio=10 tid=0x09489800 nid=0x5391 waiting on condition

JNI global references: 854

Heap
 PSYoungGen      total 14976K, used 1029K [0xa2dd0000, 0xa3e80000, 0xb39d0000)
  eden space 12864K, 8% used [0xa2dd0000,0xa2ed1530,0xa3a60000)
  from space 2112K, 0% used [0xa3c70000,0xa3c70000,0xa3e80000)
  to   space 2112K, 0% used [0xa3a60000,0xa3a60000,0xa3c70000)
 PSOldGen        total 34304K, used 0K [0x815d0000, 0x83750000, 0xa2dd0000)
  object space 34304K, 0% used [0x815d0000,0x815d0000,0x83750000)
 PSPermGen       total 16384K, used 1739K [0x7d5d0000, 0x7e5d0000, 0x815d0000)
  object space 16384K, 10% used [0x7d5d0000,0x7d782e90,0x7e5d0000)

参考: 有益的CountDownLatch和我们的JCG合作伙伴 Adrianos Dadis 遇到的棘手的Java僵局 ,位于“ Java,集成和源代码的美德”博客上

相关文章 :

翻译自: https://www.javacodegeeks.com/2011/11/beneficial-countdownlatch-and-tricky.html

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值