您当前的位置: 首页 > 

Dongguo丶

暂无认证

  • 1浏览

    0关注

    472博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

锁的活跃性(死锁、活锁、饥饿)

Dongguo丶 发布时间:2021-09-16 09:02:59 ,浏览量:1

死锁

锁是个非常有用的工具,运用场景非常多,它使用起来非常简单,而且易于理解。但 同时它也会带来一些困扰,那就是可能会引起死锁,一旦产生死锁,就会造成系统功能不可 用。

定义

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

死锁的产生的一些特定条件:

1、互斥条件:即一个资源只能被一个线程占用,直到被该线程释放 。

2、请求和保持条件:一个线程因请求资源而发生阻塞时,对已获得的资源保持不放。

3、不剥夺条件:一个资源被一个线程占用时,其他线程都无法对这个资源剥夺占用。

4、循环等待条件:当发生死锁时,所等待的线程会形成一个环路(类似于死循环),造成永久阻塞。

造成死锁必须达到4个条件,避免死锁,只需不满足其中一个条件即可,前三个都是作为锁要符合的条件,所以要避免死锁就要打破第四个条件

产生死锁的主要原因

系统资源不足

进程运行推进的顺序不合适

资源分配不当

image-20210906095827548

package com.dongguo.sync;

import jdk.internal.dynalink.beans.StaticClass;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/8/24 0024-14:25
 * @description: 模拟死锁
 */
public class DeadLock {
    static Object obj1 = new Object();
    static Object obj2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (obj1) {
                System.out.println(Thread.currentThread().getName() + "持有锁1,试图获取锁2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println(Thread.currentThread().getName() + "持有锁1,获取锁2成功");
                }
            }
        }, "ThreadA").start();
        new Thread(() -> {
            synchronized (obj2) {
                System.out.println(Thread.currentThread().getName() + "持有锁2,试图获取锁1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println(Thread.currentThread().getName() + "持有锁2,获取锁1成功");
                }
            }
        }, "ThreadB").start();
    }
}

运行结果:

image-20210903212133572

这段代码会引起死锁,使线程ThreadA和线程ThreadAB互相等待对方释放锁。

这段代码只是演示死锁的场景,在现实中你可能不会写出这样的代码。但是,在一些更为 复杂的场景中,你可能会遇到这样的问题,比如ThreadA拿到锁之后,因为一些异常情况没有释放锁 (死循环)。又或者是ThreadA拿到一个数据库锁,释放锁的时候抛出了异常,没释放掉 。那么ThreadAB

获取锁时会等待ThreadA去释放锁,这样就造成了死锁。

死锁验证

一旦出现死锁,业务是可感知的,因为不能继续提供服务了,那么只能通过dump线程查看 到底是哪个线程出现了问题

使用java命令行jstack方式
C:\Users\Administrator>jps
4288 RemoteMavenServer36
10968 Jps
17352 Launcher
13212 DeadLock
13436

C:\Users\Administrator>jstack 13212
2021-08-24 14:32:51
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.301-b09 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x000002b418b40000 nid=0x3fa0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"ThreadB" #12 prio=5 os_prio=0 tid=0x000002b42dc62800 nid=0x2ea4 waiting for monitor entry [0x000000c4c43ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.dongguo.sync.DeadLock.lambda$main$1(DeadLock.java:39)
        - waiting to lock  (a java.lang.Object)
        - locked  (a java.lang.Object)
        at com.dongguo.sync.DeadLock$$Lambda$2/1480010240.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"ThreadA" #11 prio=5 os_prio=0 tid=0x000002b42dc1a800 nid=0x2a00 waiting for monitor entry [0x000000c4c42ff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.dongguo.sync.DeadLock.lambda$main$0(DeadLock.java:26)
        - waiting to lock  (a java.lang.Object)
        - locked  (a java.lang.Object)
        at com.dongguo.sync.DeadLock$$Lambda$1/2074407503.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000002b42d921800 nid=0x43c0 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000002b42ce5f800 nid=0xc0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000002b42ce5e000 nid=0xc8c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000002b42ce5c800 nid=0x2830 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000002b42ce59000 nid=0x41e4 runnable [0x000000c4c3cfe000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked  (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked  (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:48)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000002b42cd7e800 nid=0x3e44 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000002b42cdd5000 nid=0xde0 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000002b42cd52800 nid=0x3004 in Object.wait() [0x000000c4c39fe000]
   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:144)
        - locked  (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000002b42cd4b800 nid=0x884 in Object.wait() [0x000000c4c38ff000]
   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:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked  (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x000002b42cd21800 nid=0x324 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000002b418b55000 nid=0xd9c runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000002b418b56800 nid=0x4034 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000002b418b58000 nid=0x431c runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000002b418b59000 nid=0x3b54 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000002b42d925000 nid=0x4108 waiting on condition

JNI global references: 317


Found one Java-level deadlock:
=============================
"ThreadB":
  waiting to lock monitor 0x000002b42cd4fa18 (object 0x00000000eb4b5288, a java.lang.Object),
  which is held by "ThreadA"
"ThreadA":
  waiting to lock monitor 0x000002b42cd522a8 (object 0x00000000eb4b5298, a java.lang.Object),
  which is held by "ThreadB"

Java stack information for the threads listed above:
===================================================
"ThreadB":
        at com.dongguo.sync.DeadLock.lambda$main$1(DeadLock.java:39)
        - waiting to lock  (a java.lang.Object)
        - locked  (a java.lang.Object)
        at com.dongguo.sync.DeadLock$$Lambda$2/1480010240.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"ThreadA":
        at com.dongguo.sync.DeadLock.lambda$main$0(DeadLock.java:26)
        - waiting to lock  (a java.lang.Object)
        - locked  (a java.lang.Object)
        at com.dongguo.sync.DeadLock$$Lambda$1/2074407503.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.


C:\Users\Administrator>

着重看最后Java stack information for the threads listed above:

ThreadB waiting to lock locked

ThreadA waiting to lock locked

Found 1 deadlock.

ThreadB 等待锁5288 ,持有锁5298

ThreadA 等待锁5298 ,持有锁5288

Found 1 deadlock.

如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到 CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查

当然也可以使用可视化工具,比如jvisualvm

image-20210903212424893

image-20210903212452430

显示的内容是一样的

Jconsole

image-20210906104531406

image-20210906104557461

如何避免死锁:

1、加锁顺序: 注意加锁顺序,保证每个线程按照同样的顺序进行加锁

当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。当然这种方式需要你事先知道所有可能会用到的锁,然而总有些时候是无法预知的。

2、加锁时限: 针对设置一个超时时间

加上一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。但是如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。

3、死锁检测:预防机制,确保第一时间发现死锁进行解决

死锁检测即每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。

避免死锁的几个常见方法。

·避免一个线程同时获取多个锁。 ·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。 ·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。 ·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,

例如

package com.dongguo.lock;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/12 0012-14:15
 * @description:
 */
public class LiveLockDemo {
    static volatile int count = 10;

    static final Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            // 期望减到 0 退出循环
            while (count > 0) {
                try {
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+" count="+count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超过 20 退出循环
            while (count  {
            //循环100次保证能够卖光票
            for (int i = 0; i  {
            for (int i = 0; i  {
            for (int i = 0; i  0) {
                count--;
                System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //解锁
            lock.unlock();
        }
    }
}

运行结果:

T1卖票成功,还剩99张票!
T1卖票成功,还剩98张票!
T1卖票成功,还剩97张票!
T1卖票成功,还剩96张票!
T1卖票成功,还剩95张票!
T1卖票成功,还剩94张票!
T1卖票成功,还剩93张票!
T1卖票成功,还剩92张票!
T1卖票成功,还剩91张票!
T1卖票成功,还剩90张票!
T1卖票成功,还剩89张票!
T1卖票成功,还剩88张票!
T1卖票成功,还剩87张票!
T1卖票成功,还剩86张票!
T1卖票成功,还剩85张票!
T1卖票成功,还剩84张票!
T1卖票成功,还剩83张票!
T1卖票成功,还剩82张票!
T1卖票成功,还剩81张票!
T1卖票成功,还剩80张票!
T1卖票成功,还剩79张票!
T1卖票成功,还剩78张票!
T1卖票成功,还剩77张票!
T1卖票成功,还剩76张票!
T1卖票成功,还剩75张票!
T1卖票成功,还剩74张票!
T1卖票成功,还剩73张票!
T1卖票成功,还剩72张票!
T1卖票成功,还剩71张票!
T1卖票成功,还剩70张票!
T1卖票成功,还剩69张票!
T1卖票成功,还剩68张票!
T1卖票成功,还剩67张票!
T1卖票成功,还剩66张票!
T1卖票成功,还剩65张票!
T1卖票成功,还剩64张票!
T1卖票成功,还剩63张票!
T1卖票成功,还剩62张票!
T1卖票成功,还剩61张票!
T1卖票成功,还剩60张票!
T1卖票成功,还剩59张票!
T1卖票成功,还剩58张票!
T1卖票成功,还剩57张票!
T1卖票成功,还剩56张票!
T1卖票成功,还剩55张票!
T1卖票成功,还剩54张票!
T1卖票成功,还剩53张票!
T1卖票成功,还剩52张票!
T1卖票成功,还剩51张票!
T1卖票成功,还剩50张票!
T1卖票成功,还剩49张票!
T1卖票成功,还剩48张票!
T1卖票成功,还剩47张票!
T1卖票成功,还剩46张票!
T1卖票成功,还剩45张票!
T1卖票成功,还剩44张票!
T1卖票成功,还剩43张票!
T1卖票成功,还剩42张票!
T1卖票成功,还剩41张票!
T1卖票成功,还剩40张票!
T1卖票成功,还剩39张票!
T1卖票成功,还剩38张票!
T1卖票成功,还剩37张票!
T1卖票成功,还剩36张票!
T1卖票成功,还剩35张票!
T1卖票成功,还剩34张票!
T1卖票成功,还剩33张票!
T1卖票成功,还剩32张票!
T1卖票成功,还剩31张票!
T1卖票成功,还剩30张票!
T1卖票成功,还剩29张票!
T1卖票成功,还剩28张票!
T2卖票成功,还剩27张票!
T2卖票成功,还剩26张票!
T2卖票成功,还剩25张票!
T2卖票成功,还剩24张票!
T2卖票成功,还剩23张票!
T2卖票成功,还剩22张票!
T2卖票成功,还剩21张票!
T2卖票成功,还剩20张票!
T2卖票成功,还剩19张票!
T2卖票成功,还剩18张票!
T2卖票成功,还剩17张票!
T2卖票成功,还剩16张票!
T2卖票成功,还剩15张票!
T2卖票成功,还剩14张票!
T2卖票成功,还剩13张票!
T2卖票成功,还剩12张票!
T2卖票成功,还剩11张票!
T2卖票成功,还剩10张票!
T2卖票成功,还剩9张票!
T2卖票成功,还剩8张票!
T2卖票成功,还剩7张票!
T2卖票成功,还剩6张票!
T2卖票成功,还剩5张票!
T2卖票成功,还剩4张票!
T2卖票成功,还剩3张票!
T2卖票成功,还剩2张票!
T2卖票成功,还剩1张票!
T2卖票成功,还剩0张票!

所有的票都被线程t1、t2卖掉了,t3线程没有运行。

比如ReentrantReadWriteLock中

一旦读操作比较多的时候,想要获取写锁就变得比较困难了, 假如当前1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那1个写线程就悲剧了 因为当前有可能会一直存在读锁,而无法获得写锁,根本没机会写

如何避免饥饿问题

a,设置合理的优先级 b,使用公平锁来代替synchronized这种互斥锁(可以使用邮戳锁StampedLock替代ReentrantReadWriteLock)

关注
打赏
1638062488
查看更多评论
立即登录/注册

微信扫码登录

0.0416s