赞
踩
回顾synchronized
package thread; class Counter2 { private int count = 0; void add() { synchronized (this) { count++; } } int get() { return count; } } public class Demo21 { public static void main(String[] args) throws InterruptedException { Counter2 counter2 = new Counter2(); Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { counter2.add(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { synchronized (counter2) { counter2.add(); } } }); t2.start(); t1.start(); t2.join(); t1.join(); System.out.println("count = " + counter2.get()); } }
上述线程t2的代码相当于
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (counter2) {
synchronized (counter2){
count++;
}
}
}
});
假设t2先启动(t1先不考虑),t2第一次加锁,肯定能加锁成功,当t2尝试第二次加锁的时候,此时counter2变量,属于已经被锁定的状态了,针对一个已经被锁定的对象加锁,就会出现阻塞等待,阻塞到对象被解锁为止
这种情况下,就叫“死锁”
但是在实际上述过程中,对于synchronized是不适用的,synchronized上述代码是不会出现死锁的,但是如果是C++/Pyhton的锁就会出现死锁
注意:当加了多层锁的时候,代码执行到哪里要真正进行解锁呢
一定是在遇到最外层的
}
,那么,如何确定遇到的}
是最外层的,运行时,给锁对象里也维护一个计数器(int n),每次遇到{
,n++(只有第一次才真正加锁),当遇到}
就n–,当n减到0了,才真正解锁
死锁有三种比较典型的场景
(1)场景一:锁是不可重入锁,并且一个线程针对一个锁对象,连续加锁两次,通过引入可重入锁,可以解决上述问题
(2)场景二:两个线程,两把锁
(3)场景三:N个线程,M把锁
有线程1和线程2,以及锁A和锁B,现在线程1和2都需要获取到锁A和锁B(拿到锁A之后,不释放A,继续获取锁B),即先让两个线程分别拿到一把锁,然后去尝试获取对方的锁
举个例子:健康码崩了,程序员回到公司修复bug,被保安拦住了
类似于:家钥匙锁车里了,车钥匙锁家里了
package thread; public class Demo22 { public static void main(String[] args) throws InterruptedException { Object locker1 = new Object(); Object locker2 = new Object(); Thread t1 = new Thread(() ->{ synchronized (locker1){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (locker2){ System.out.println("t1 获取了两把锁"); } } }); Thread t2 = new Thread(()->{ synchronized (locker2){ try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (locker1){ System.out.println("t2 获取了两把锁"); } } }); t1.start(); t2.start(); t1.join(); t2.join(); } }
上述代码:
当遇到死锁问题,可以通过上述调用栈+状态进行定位
随着线程数目/锁的个数增加,此时情况就更复杂了,更容易出现死锁
哲学家就餐问题
现在桌子上均匀摆放有5根筷子,总共有5位哲学家,也就是说每位哲学家左右两边各一双筷子,每一位哲学家要做的事情就是放下筷子或者拿起左右两根筷子,但是每个哲学家什么时候放下筷子,什么时候拿起左右两根筷子是不确定的(抢占式执行)
如果出现下列极端情况,就相当于死锁了
上述就是非常典型的死锁情况
死锁是非常严重的问题:死锁会使线程被卡住,没办法继续工作了,而且死锁这种bug,往往都是概率性出现
死锁的四个必要条件
必要条件缺一不可,任何一个死锁的场景,都必须同时具备上述四点
当代码中,确实需要用到多个线程获取多把锁,一定要记得约定好加锁的顺序,就可以有效避免死锁了
package thread; import java.util.Scanner; public class Demo23 { private static int count = 0; public static void main(String[] args) { Thread t1 = new Thread(()->{ while (count==0){ } System.out.println("t1执行结束"); }); Thread t2 =new Thread(()->{ Scanner scanner = new Scanner(System.in); System.out.println("请输入一个整数:"); count = scanner.nextInt(); }); t1.start(); t2.start(); } }
上述代码,当t2线程读到一个不为0的整数的时候,预期t1就会结束循环,但是结果并非如此
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。