🌈个人主页:努力学编程’
⛅个人推荐:
c语言从初阶到进阶
JavaEE详解
数据结构
⚡学好数据结构,刷题刻不容缓:点击一起刷题
🌙心灵鸡汤:总有人要赢,为什么不能是我呢
🌈🌈🌈死锁的概念
对于线程安全问题,我们在上一篇文章中已经提到了一些情况,比如由于多个线程针对同一个变量修改,即类似于count++的操作,可能会由于多线程抢占式执行的特性导致count的值发生异常,其实除了这种情况之外,线程安全仍有很多我们需要注意的操作,比较典型的就是死锁
🌈🌈🌈实例死锁场景
那么到底什么事死锁呢,我们直到在上述我们提到的线程安全问题中我们解决的方法之一就是可以将count++这些操作使用synchronized关键字来使用锁将其封装起来,这就构成了锁而如果你在锁的内部,又加了一重锁,那么就可能会导致死锁,
public static Object locker=new Object();public static void main(String[] args) {Thread t1=new Thread(()->{synchronized (locker){synchronized (locker){System.out.println("hello Thread");}}});t1.start();}
此时线程的代码执行到锁1后,由于锁的互斥性,这里锁2就无法抢占到锁,必须等到锁1执行完毕之后,才能有机会抢占到锁,但是要想锁1释放锁,就要执行完其中的所有代码,但是代码显然在锁2处阻塞了,此时就会出现死锁问题了.
幸运的是我们运行这段代码是可以正常打印的,这是为什么呢,原来是因为,在java内部对这个死锁的状况做了一定的优化,简单来说,就是程序员大概率会写出这样的代码,为了能提高代码的执行效率,java就会对其做出判断,当前锁对象是否已经在外部使用过,使用过h会将内部锁失效,正常执行代码.
🌈🌈🌈死锁场景2-多个线程多把锁
假如我们现在有两个线程,两把锁,那么此时当线程1在不释放锁1的时候,同时针对锁2加锁,线程2在不释放锁2的情况下,针对锁1加锁,那么此时就会导致死锁的发生.
public class Test {public static Object locker1=new Object();public static Object locker2=new Object();public static void main(String[] args) {Thread t1=new Thread(()->{synchronized (locker1){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println("hello Thread1");}}});Thread t2=new Thread(()->{synchronized (locker2){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){System.out.println("hello Thread2");}}});t1.start();t2.start();}}
运行结果:
过程分析:
这里线程1,线程2都有可能会抢到先执行的状况,所以我们对两个线程都加sleep操作,这样就会保证两个线程都能抢到一把最外边的锁规避一个线程将两把锁都抢占的情况.实现上述的场景
此时线程1要想继续执行代码就要等到线程2释放锁2之后,才可以抢占锁2继续执行后序的代码,而对于线程2来说,要想继续执行后序的代码,就要等线程1释放锁1之后,才能针对锁1加锁然后继续完成后续的操作.所以此时就会陷入一个僵持的场面,进而导致程序陷入持续等待的场景,这也是死锁的场景,并且此时Java也无法对其作出优化操作.
🌈🌈🌈哲学家问题引起的死锁问题
在古代西欧时就提出了一个非常有意思的问题,哲学界问题,是这样的,一共有五个哲学家,坐在同一个桌子上吃饭,每个人的跟前都有一根筷子和一碗饭,对于哲学家来说他们一共做两件事一:吃饭,二:思考人生,如果大家同时思考人生,后面如果大家都要开始同时吃饭那么就会出现每个人都拿着一根筷子无法正常进食.
其实这个场景就很好的演示了关于死锁的最后一个典型案例,即一共有m把锁,n个线程,此时就可能出现上述的案例,那么如何解决这个死锁的情况呢.
这里采取的措施是这样的,我们给每一根筷子从小到大都标号,让每个哲学家都必须拿到再它面前最小标号的筷子.此时就会出现如下场景.
好的,此时按照我们的要求每个人必须要先拿到自己面前最小编号的筷子,所以此时就会导致最上面的人不能拿到筷子,那么此时最左边的人就会接着拿到5号筷子,吃饭,吃完后放下筷子,离开接着是左下方人又拿着4号筷子开始吃饭,吃完离开,以此类推,所有人都可以吃到饭了.
对应到线程中也是一样的,我们可以将所有的锁进行标号,并且每次让线程拿锁按照一定的顺序,这样就会巧妙的避开当前所说的死锁问题,这里就给大家说到这里.这里关于死锁可能还有一个话题就是银行家算法,这个确实是解决死锁的一种方法,但是由于实现该算法的代码很复杂,语法较难,所以我们日常开发中是不用这种算法的,大家感兴趣的话可以自己去网上看看.