一、死锁 死锁产生的四个必要条件
互斥条件 :进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件 :当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件 :进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件 :在发生死锁时,必然存在一个进程–资源的环形链。
死锁示例 有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程获得A对象锁,接下来想获取 B对象的锁
t2 线程获得B对象锁,接下来想获取 A对象的锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import lombok.extern.slf4j.Slf4j;import static site.weiyikai.thread.utils.Sleeper.sleep;@Slf4j(topic = "c.TestDeadLock") public class TestDeadLock { public static void main (String[] args) { test1(); } private static void test1 () { Object A = new Object (); Object B = new Object (); Thread t1 = new Thread (() -> { synchronized (A) { log.debug("lock A" ); sleep(1 ); synchronized (B) { log.debug("lock B" ); log.debug("操作..." ); } } },"t1" ); Thread t2 = new Thread (() -> { synchronized (B) { log.debug("lock B" ); sleep(0.5 ); synchronized (A) { log.debug("lock A" ); log.debug("操作..." ); } } },"t2" ); t1.start(); t2.start(); } }
运行结果
1 2 3 21 :51 :08.966 c.TestDeadLock [t2] - lock B21 :51 :08.966 c.TestDeadLock [t1] - lock A
二、定位死锁 检测死锁可以使用jconsole
工具,或者使用jps定位进程id,再用jstack
定位死锁。
2.1 jconsole工具检测死锁 (1)选择要监测死锁的进程
(2)选择线程->点击下方检测死锁按钮
(3)产生死锁的线程和信息
2.2 通过jstack
命令定位死锁 (1)通过jps
命令定位进程id
1 2 3 4 5 D:\Codes\idea\thread_demo>jps7456 JConsole9156 TestDeadLock1128 Launcher12264 Jps
(2)通过 jstack
命令定位死锁
1 D:\Codes\idea\thread_demo>jstack 9156
运行部分结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ............... Java stack information for the threads listed above: ==================================================="t2" : at com.lilinchao.concurrent.demo_03.TestDeadLock.lambda$test1$1 (TestDeadLock.java:37 ) - waiting to lock <0x00000000d7f90678 > (a java.lang.Object) - locked <0x00000000d7f90688 > (a java.lang.Object) at com.lilinchao.concurrent.demo_03.TestDeadLock$$Lambda$2 /1416233903. run(Unknown Source) at java.lang.Thread.run(Thread.java:748 )"t1" : at com.lilinchao.concurrent.demo_03.TestDeadLock.lambda$test1$0 (TestDeadLock.java:26 ) - waiting to lock <0x00000000d7f90688 > (a java.lang.Object) - locked <0x00000000d7f90678 > (a java.lang.Object) at com.lilinchao.concurrent.demo_03.TestDeadLock$$Lambda$1 /787387795. run(Unknown Source) at java.lang.Thread.run(Thread.java:748 ) Found 1 deadlock.
三、哲学家就餐问题
有五位哲学家,围坐在圆桌旁。
他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
如果筷子被身边的人拿着,自己就得等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import site.weiyikai.concurrent.utils.Sleeper;import lombok.extern.slf4j.Slf4j;public class Test05 { public static void main (String[] args) { Chopstick c1 = new Chopstick ("1" ); Chopstick c2 = new Chopstick ("2" ); Chopstick c3 = new Chopstick ("3" ); Chopstick c4 = new Chopstick ("4" ); Chopstick c5 = new Chopstick ("5" ); new Philosopher ("苏格拉底" , c1, c2).start(); new Philosopher ("柏拉图" , c2, c3).start(); new Philosopher ("亚里士多德" , c3, c4).start(); new Philosopher ("赫拉克利特" , c4, c5).start(); new Philosopher ("阿基米德" , c5, c1).start(); } }@Slf4j(topic = "c.Philosopher") class Philosopher extends Thread { Chopstick left; Chopstick right; public Philosopher (String name, Chopstick left, Chopstick right) { super (name); this .left = left; this .right = right; } @Override public void run () { while (true ) { synchronized (left) { synchronized (right) { eat(); } } } } private void eat () { log.debug("eating..." ); Sleeper.sleep(0.5 ); } }class Chopstick { String name; public Chopstick (String name) { this .name = name; } @Override public String toString () { return "筷子{" + name + '}' ; } }
运行结果
1 2 3 4 5 6 7 8 22 :54 :29.039 c.Philosopher [赫拉克利特] - eating...22 :54 :29.039 c.Philosopher [苏格拉底] - eating...22 :54 :29.544 c.Philosopher [亚里士多德] - eating...22 :54 :29.544 c.Philosopher [阿基米德] - eating...22 :54 :30.045 c.Philosopher [阿基米德] - eating...22 :54 :30.545 c.Philosopher [赫拉克利特] - eating...22 :54 :31.046 c.Philosopher [亚里士多德] - eating...
使用 jconsole 检测死锁
线程各自持有各自的资源无法释放。
这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情况。
四、活锁 活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。
活锁是一种情景,当两个或多个线程不停地尝试执行某个操作,但它们的执行被彼此的操作阻塞时,就会出现活锁。例如,在一个多人游戏中,两个角色相遇,他们的行动互相阻止对方移动,最终引发活锁。
在这种情况下,两个线程可以一直卡在互相阻止对方的操作上,导致无法结束程序。解决活锁的方法是引入一些调度机制,在两个线程中有一方先停止操作,让另一方继续执行,然后再轮到其它方停止操作。这样,两个线程可以在不互相阻塞的时候完成任务,从而避免了活锁的发生。
因此,在编程时,需要遵循一些规范,避免出现活锁的情况,以保证线程的正常运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import lombok.extern.slf4j.Slf4j;import static site.weiyikai.concurrent.utils.Sleeper.sleep;@Slf4j(topic = "c.TestLiveLock") public class TestLiveLock { static volatile int count = 10 ; static final Object lock = new Object (); public static void main (String[] args) { new Thread (() -> { while (count > 0 ) { sleep(0.2 ); count--; log.debug("count: {}" , count); } }, "t1" ).start(); new Thread (() -> { while (count < 20 ) { sleep(0.2 ); count++; log.debug("count: {}" , count); } }, "t2" ).start(); } }
运行结果
1 2 3 4 5 6 7 8 23 :04 :21.041 c.TestLiveLock [t1] - count: 9 23 :04 :21.041 c.TestLiveLock [t2] - count: 9 23 :04 :21.246 c.TestLiveLock [t2] - count: 10 23 :04 :21.246 c.TestLiveLock [t1] - count: 9 23 :04 :21.447 c.TestLiveLock [t2] - count: 10 23 :04 :21.447 c.TestLiveLock [t1] - count: 9 23 :04 :21.652 c.TestLiveLock [t2] - count: 10 ......................
分析
t1和t2两个线程,一个执行count--
,期望减到0的时候退出循环,一个执行count++
,期望加到20退出循环,但是永远不能都退出循环。
五、饥饿 很多教程中把饥饿定义为,*一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束 *,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题