一、死锁
一个线程需要获得多把锁,就容易出现死锁。
比如此时有两把锁,分别是A和B。线程1首先需要获得A,然后获得B;线程2首先需要获得B,然后获得A。于是两个线程就一直等待对方释放锁。
二、死锁之哲学家就餐
一个圆桌,五个人,五只筷子,每个人吃饭需要拿起左右两边的筷子吃,设计代码:
设计筷子类
class Chopstick{String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" +"name='" + name + '\'' +'}';}
}
设计哲学家类
@Slf4j(topic = "c.phi")
class philosopher extends Thread{Chopstick left;Chopstick right;public philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}private void eat(){log.debug("吃饭吃饭");try {Thread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void run(){while (true){synchronized (left){synchronized (right){eat();}}}}
}
main中测试代码如下:
public class testPhilosopher {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();}
}
可以看到,执行不下去了:
死锁定位
我们先用jps命令定位进程id,可以看到id为6620
再使用jstack 6620查看:这样就能看到死锁的准确定位了
Found one Java-level deadlock:
=============================
"马克思":waiting to lock monitor 0x0000000020521d58 (object 0x000000076bf84248, a com.smy.day4.Chopstick),which is held by "柏拉图"
"柏拉图":waiting to lock monitor 0x000000001cdd2868 (object 0x000000076bf84288, a com.smy.day4.Chopstick),which is held by "康德"
"康德":waiting to lock monitor 0x000000001cdd27b8 (object 0x000000076bf842c8, a com.smy.day4.Chopstick),which is held by "尼采"
"尼采":waiting to lock monitor 0x000000001cdcff28 (object 0x000000076bf84308, a com.smy.day4.Chopstick),which is held by "庄子"
"庄子":waiting to lock monitor 0x000000001cdcffd8 (object 0x000000076bf84348, a com.smy.day4.Chopstick),which is held by "马克思"at com.smy.day4.philosopher.run(testPhilosopher.java:62)- waiting to lock <0x000000076bf84288> (a com.smy.day4.Chopstick)- locked <0x000000076bf84248> (a com.smy.day4.Chopstick)
"康德":at com.smy.day4.philosopher.run(testPhilosopher.java:62)- waiting to lock <0x000000076bf842c8> (a com.smy.day4.Chopstick)- locked <0x000000076bf84288> (a com.smy.day4.Chopstick)
"尼采":at com.smy.day4.philosopher.run(testPhilosopher.java:62)- waiting to lock <0x000000076bf84308> (a com.smy.day4.Chopstick)- locked <0x000000076bf842c8> (a com.smy.day4.Chopstick)
"庄子":at com.smy.day4.philosopher.run(testPhilosopher.java:62)- waiting to lock <0x000000076bf84348> (a com.smy.day4.Chopstick)- locked <0x000000076bf84308> (a com.smy.day4.Chopstick)Found 1 deadlock.
三、活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。
比如:cnt=5
线程1一直循环,退出条件是cnt <10,每次-1,sleep1秒
线程2一直循环,退出条件是cnt >0,每次+1,sleep1秒
这样cnt一直保持加一减一,谁也满足不了条件,就形成了活锁。
四、饥饿
一个线程始终得不到 CPU 调度执行,也不能够结束。
比如哲学家问题,我们可以用顺序加锁的方式解决死锁。
因为他们要持有的筷子是
1 2
2 3
3 4
4 5
5 1
这样,有可能每个人都持有1,2,3,4,5导致死锁,那我们把最后一个改成1 5,这样就破坏了死锁的条件,1这把筷子会被竞争。
但是,当我们这样修改之后,马克思一直吃不上饭,进入了饥饿状态。
这是吃饭时间为300ms的情况:
吃饭时间为10ms时:可以发现,这时候某个线程总能抢到筷子,但是有的线程永远拿不到,饥饿的线程越来越饥饿。庄子需要4 5筷子,马克思需要1 5,但是马克思抢不到饭,因为他执行的晚,一直抢不到1筷子,所以庄子需要的5筷子就没人用,所以庄子总是能顺利进行。