几张图解释清楚死锁+wait和notify的搭配使用

本文深入探讨了可重入锁的概念,通过例子解释为何一个线程可以对同一对象加锁多次而不会造成死锁。接着介绍了死锁的四种必要条件,并通过哲学家吃面的故事生动展示死锁场景。解决死锁的方法是制定合理的加锁顺序规则。此外,文章还详细阐述了wait和notify方法在多线程同步中的作用,以及它们的正确使用方式,包括wait后的操作和notify的唤醒机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

几张图解释清楚死锁+wait和notify的搭配使用

死锁🚕

  1. synchronized在上一篇博客中进行介绍时,提到,一旦一个对象被上锁,那别的线程就无法再给这个对象上锁,但如果这个线程是自身呢?也就是说一个线程尝试给同一个对象上锁两次会出现什么结果?比如见下面的代码;

    class Counter1{
        private int count=0;
        public  synchronized void increase1(){
            this.count++;
        }
        public synchronized void increase2(){
            this.count++;
        }
        public int getCount(){
            return this.count;
        }
    }
    public class Demo4 {
        public static void main(String[] args) {
            Counter1 counter=new Counter1();
            counter.increase1();
            counter.increase2();
            System.out.println(counter.getCount());
        }
    }
    //打印2
    

    上述代码中,main线程中先后执行了increase1和increase2,这两个操作都会先给Counter1对象进行上锁,按照synchronized的理解,主线程的任务是执行完两次increase才算完成任务,所以说明了一个线程对同一个对象加锁两次,但是代码却正常运行出了结果.

    这正是由于synchronized是一个可重入锁

    可重入锁:同一个线程使用synchronized给同一个对象进行上锁,没有任何问题,此时可重入锁内部会记录当前的锁被哪个线程所持有,并且记录当前线程已经给这个锁上几次了,此时连续上锁的操作就不会导致一个死锁.此时会记录该线程已经为这个锁对象上了多少次锁.

  2. 一把锁,一个线程的情况:

    如果synchronized不是可重入锁,一个线程尝试先后两次给同一个对象进行上锁,由于synchronized将赋予锁不可被抢占,并且呢线程只会在synchronized锁上的代码块都执行完了才会把锁对象进行释放,如果出现下面的代码:

    synchronized(this){
        synchronized(this){
            ...
        }
    }
    

    不给上两次锁我就没办法把任务执行完,任务直行不完,我又办法释放锁,陷入僵局…即死锁.

  3. 两个线程两把锁

    两个线程先各自锁上一个对象,但是每个线程的任务后续必须还要获取到对方已经获取到的锁,才能把整个任务给执行完,但是每个线程任务执行不完,又都没办法把各自锁持有的锁给释放,并且锁天生有不可被抢占.所以陷入僵局…即死锁

  4. N个线程M把锁

    形成环路等待(还是由于不可抢占导致),就会陷入僵局…即死锁.

    比如有一个故事:哲学家吃面条

    故事:每个哲学家都非常的固执,都是要先拿起左手边的筷子,再拿起右手边的筷子(左手边没筷子了,就拿右手边的筷子),再吃面条,再放下筷子.只有没有拿起一双筷子,哲学家就会一直等着,直到能拿起一双筷子了,就会吃面条.

    但是因为都先拿起了左手边的筷子(倒霉鬼先拿起右手边的筷子),等到要拿右手边的时候,筷子总被其他人占着,但是每个哲学家只有拿不到一双筷子就会一直等,那此时就会陷入僵局,谁也别吃面条了.这就是形成了死锁.

  5. 总结死锁出现的必要条件:

    1. 锁不可被抢占
    2. 锁是互斥使用的(一个时刻,锁最多只能被一个线程所持有)
    3. 请求和等待,锁被别的线程持有了,那本线程就会一直等着锁对象被释放,然后去尝试上锁
    4. 环路等待,就如上述的哲学及吃面条一样.
  6. 如何解决死锁问题

    只要约定好加锁的规则,就可以避免死锁

    比如上述哲学家吃面条的故事,给筷子事先编号:

    并且每个哲学家都会先拿编号小的筷子,有编号更小的筷子被别人拿走了,我就先等着,等到小编号的筷子被释放了,我才会去拿小编号筷子.

    第二步:拿身边较大编号的筷子:

    假设老铁1先拿起一双筷子,他吃完就可以放下1,5两根筷子,那此时老铁2就可以拿到1号筷子,5号老铁就可以拿到5号筷子,那老铁5也可以吃面条了,吃完面条放下筷子,4号老铁也可以吃面条,然后是3号老铁,最后是2号老铁,最终大家都吃到了面条,场面一片和谐…


    wait()和notify()的使用🌊

    等待和通知

    前面博客中有使用到join,注意join()表明的是谁等谁,如果在main线程中调用t.join(),意思是mian线程再等t线程执行任务完成,mian线程再从这一行接着往下执行.

    此处的wait和notify一般搭配synchronized进行使用.目的是让随机调度线程变得有固定的调度顺序.

    wait和notify都是object的方法.谁调用wait方法,谁(某个线程)就会陷入阻塞(waiting状态),当有另一个线程使用notify通知,进行唤醒之后,方可从刚才阻塞的代码继续往下执行.


    1. wait调用后会干三件事:

      1. 释放所对象
      2. 等待其他线程来唤醒
      3. 重新获取到锁,并接着阻塞的代码继续往下执行
    2. wait和notify必须是针对同一个锁对象来进行操作

    3. wait和notify都必须先把所对象给锁上了才能进一步使用

    4. 具体:

      public class Demo5 {
          private static Object locker=new Object();
          public static void main(String[] args) {
              Thread t1=new Thread(()->{
                  synchronized (locker){
                      try {
                          System.out.println("wait之前");
                          locker.wait();
                          System.out.println("wait之后");
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              });
              t1.start();
      
              Thread t2=new Thread(()->{
                  synchronized (locker){
                      System.out.println("notify之前");
                      locker.notify();
                      System.out.println("notify之后");
                  }
              });
              t2.start();
          }
      }
      //打印:
      wait之前
      notify之前
      notify之后
      wait之后
      
    5. notify即使去尝试唤醒一个没进行wait的线程时,也不要紧.

    6. 如果针对同一个锁先后有10个线程对其进行了wait,之后有一个线程对locker进行为唤醒,此时是随机唤醒10个waiting中的一个线程.但如果使用**notifyAll()**就会唤醒所有waiting中的线程哦(都是针对locker正在waiting的线程才行哦)


    conclusion⛵️

    1. 什么是可重入锁
    2. 什么是死锁
    3. 死锁有哪些情形
    4. 死锁怎么取解决
    5. wait和notify怎么去使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值