一个线程需要获得多把锁,就容易出现死锁。
比如此时有两把锁,分别是A和B。线程1首先需要获得A,然后获得B;线程2首先需要获得B,然后获得A。于是两个线程就一直等待对方释放锁。
一个圆桌,五个人,五只筷子,每个人吃饭需要拿起左右两边的筷子吃,设计代码:
设计筷子类
class Chopstick{
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public 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();
}
}
@Override
public 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筷子就没人用,所以庄子总是能顺利进行。