💐专栏导读
本篇文章收录于多线程,也欢迎翻阅博主的其他文章,可能也会让你有不一样的收获😄
🍁JavaSE 🌺多线程 🍂数据结构
💐synchrosized的可重入特性及死锁
可重入特性就是:当一个线程针对一个对象同时加锁两次,不会构成死锁,这样的特性称为可重入性;
例如下图例子:
为了防止上述死锁情况,synchrosized就引入了可重入性解决;
线程在加锁时,在这个锁对象内部,它会记录是对哪个线程加了锁,当对同一个线程再次进行加锁时,就会判断该线程是不是同一个线程并且是否已经持有了锁,如果已经有了锁,那么也会重复进行加锁,不会导致死锁现象;
**那么,问题就来了,如果加两次锁,在 }2 时是否应该解锁呢?**答案:不能释放锁
如果加了n次锁呢?该怎么去释放呢?
答案:在锁对象中,不仅会记录对哪个线程加了锁,还会有一个计数器记录加锁的次数;当每次执行完一个加锁的代码块时,计数器就会减1,一直到最后一个锁时,才会释放锁;
关于死锁:
1.在Java中,如果一个线程对同一个锁连续加锁两次,不会造成死锁现象
2.如果两个线程,两把锁,每个线程都加两个不同的锁(嵌套加锁),就会造成死锁现象
例如:让线程1先获取锁1,线程2获取锁2,然后在锁1的内部再尝试获取锁2, 再锁2的内部再尝试获取锁1
public static void main(String[] args) {
//定义两把锁
Object lock1 = new Object();
Object lock2 = new Object();
//让线程1嵌套获取两把锁
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
//此处睡眠很重要,如果没有睡眠,线程1可能就会一下子把两把锁都获取了,就构不成死锁现象了
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock2) {
System.out.println("thread1加锁成功");
}
}
});
//让线程2嵌套获取两把锁
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock1) {
System.out.println("thread2加锁成功");
}
}
});
thread1.start();
thread2.start();
}
3.n个线程,m把锁,更容易出现死锁问题,例如哲学家就餐问题:
哲学家就餐问题:
死锁是一个比较严重的bug,那如何避免/解决死锁呢?
💡如何避免/解决死锁
要想避免死锁,就要先直到死锁是怎么形成的,这样才能对症下药,导致死锁的四个必要条件:
1.互斥使用:当线程1获取锁之后,线程2也想获取同一把锁,就会阻塞等待(锁的特性)
2.不可抢占:当线程1已经获取到锁之后,线程2不能强行抢占锁(锁的特性)
3.请求保持:一个线程尝试获取多把锁(一个线程获取到锁1之后,还想尝试获取锁2,此时锁1也并未解锁)例如上面的
4.循环等待:线程获取锁时,形成了环路;例如,上面哲学家同时拿起左边的筷子
第一点和第二点是锁的特性,如果想要解决死锁,就要破坏第三点和第四点,
对于第三点来讲,只要避免两把不同的锁嵌套获取即可
对于第四点来讲,可以约定给所有的锁进行一个编号,规定所有的线程只能按顺序先获取编号小的锁,然后获取编号大的锁,例如:
以上虽然时嵌套加锁的,但是并未形成环路,得到lock1锁的线程执行,未获得lock1的线程阻塞等待,并且也无法获得lock2