Skip to main content

并发Liveness问题

huhxAbout 3 minjavaConcurrency-ToolkitConcurrency

多个线程协同工作,总是会遇到一些莫名其妙的情况。有可能出现多个线程敌不动我不动的情形,都在等待对方线程做些什么。有的线程不积极抢不过其他线程,可能一直没机会得到调度。也有可能多个线程抢占某个资源,势均力敌互不相让,结果一直在拉扯不休。这些问题统称为并发的Liveness问题,下面我们就无情揭开它们的面纱。

死锁Deadlock

死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又相互等对方释放锁,此时若无外力干预,这些线程则一直处理阻塞的假死状态,形成死锁。

死锁的例子

A同学抢了B同学的钢笔,B同学抢了A同学的书,两个人都相互占用对方的东西,都在让对方先还给自己自己再还,这样一直争执下去等待对方还而又得不到解决。

public class DeadLock {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        var thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println(Thread.currentThread().getName() + " get lock1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " need lock2");
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName() + " get lock2");
                }
            }
        });

        var thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println(Thread.currentThread().getName() + " get lock2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " need lock1");
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName() + " get lock1");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

输出结果:

Thread-1 get lock2
Thread-0 get lock1
Thread-0 need lock2
Thread-1 need lock1

死锁的排查

  • 执行上述的程序,然后使用jps -l
64016 com.huhx.thread.liveness.DeadLock
64017 org.jetbrains.jps.cmdline.Launcher
2392 
64460 jdk.jcmd/sun.tools.jps.Jps
88127 
  • 然后使用jstack 64016,然后可以看到包含如下的输出信息
Java stack information for the threads listed above:
===================================================
"Thread-0":
        at com.huhx.thread.liveness.DeadLock.lambda$main$0(DeadLock.java:20)
        - waiting to lock <0x000000070ffed740> (a java.lang.Object)
        - locked <0x000000070ffed730> (a java.lang.Object)
        at com.huhx.thread.liveness.DeadLock$$Lambda$14/0x0000000800c031f0.run(Unknown Source)
        at java.lang.Thread.run(java.base@18.0.2.1/Thread.java:833)
"Thread-1":
        at com.huhx.thread.liveness.DeadLock.lambda$main$1(DeadLock.java:35)
        - waiting to lock <0x000000070ffed730> (a java.lang.Object)
        - locked <0x000000070ffed740> (a java.lang.Object)
        at com.huhx.thread.liveness.DeadLock$$Lambda$15/0x0000000800c033f8.run(Unknown Source)
        at java.lang.Thread.run(java.base@18.0.2.1/Thread.java:833)

Found 1 deadlock.

从上述打印信息我们知道:

dead-lock
dead-lock

饥饿Starvation

我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源

饥饿的例子

public class StarvationLock {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        var thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " owned lock");
                while (true) {
                }
            }
        });

        var thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + " get lock");
            }
        });

        thread1.start();
        thread2.start();
    }
}

输出结果:

Thread-0 owned lock

活锁Livelock

活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。当多线程中出现了相互谦让,都主动将资源释放给别的线程使用,这样这个资源在多个线程之间跳动而又得不到执行,这就是活锁

活锁的例子

两个迎面走来的两个绅士互相礼让,A想让B通过往右边礼让,同时B想让A通过也往右礼让。此时AB都在右边,然后两人又同时开始礼让对方,于是两人又都在左边。于是两开始了无限次的礼让,但是仍旧没有一个人能够通过。这就是活锁

LiveLock
LiveLock

FAQ

总结

参考