大飞老师带你看线程(线程通讯-wait和notify)

本文介绍了Java线程通讯,指出简单的共享资源方式实操性不强,真正的通讯需借助JDK的wait/notify方法。通过生产者与消费者的例子,说明其唤醒和通知等待功能。还从源码分析了wait和notify方法的操作,最后提到多人场景下使用wait/notifyAll可避免死锁。

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

引子

线程通讯表示线程间可以进行数据交流, 最简单的实现方式就是使用共享资源, 看下面代码:

//共享资源
public class Resource {
    private int resource;
    public synchronized int getResource(){
        return resource;
    }
    public synchronized void setResource(){
        resource++;
    }
}
public class App {
    public static void main(String[] args) {
        //2线程公用一个资源
        final Resource resource = new Resource();
        new Thread(new Runnable() {
            public void run() {
                resource.getResource();
            }
        }, "t1").start();
        new Thread(new Runnable() {
            public void run() {
                resource.setResource();
            }
        }, "t2").start();
    }
}

分析:
上面代码定义一个resource类,线程t1, t2 共享操作, 实现t1,t2线程的共享.但是, 单纯使用这种方式, 实操性不强, 无法做到真正的通讯. 这个可以类比日常生活中的通讯, 比如:打电话. A拨号找B, 会响铃通知B电话来了.通话结束后, B挂断电话, A就知道通话结束.如果A电话没来,B先守着.而线程通讯更多指向于AB线程间能具有通知,等待的功能. 那如何做到? 答案是: JDK的wait/notify方法

解析

java.lang.Object类下面有3个方法: wait(), notify()和notifyAll() 完成线程通讯.其操作原理非常简单, 一个线程调用任意一个对象的wait方法, 该线程马上进入非运行状态, 当且仅当其他线程调用同一个对象的notify或者notifyAll方法, 这个线程才有机会转换成等待状态或者可运行状态. 这里强调下, 线程想要调用对象的wait/notify/notifyAll方法, 就必须持有对象的锁.换句话说, 在调用wait跟notify方法时, 必须使用synchronized.

那这个与线程通讯有毛关系? 看下经典例子: 生产者与消费者
需求: 一个包子店, 只有一个面包师, 一个蒸笼.蒸笼只能放满10个包子, 当满10个包子时,面包师可以休息.如果包子卖完了,就得开始弄包子.而顾客一次买一个包子, 当包子就只能等啦.

public class BaoZiShop {
    //包子个数
    private  int baoziNum = 0;

    public int getBaoziNum() {
        return baoziNum;
    }

    public void setBaoziNum(int baoziNum) {
        this.baoziNum = baoziNum;
    }
}
//面包师
public class Baker implements  Runnable {

    private BaoZiShop shop;
    public Baker(BaoZiShop shop) {
        this.shop = shop;
    }
    public void run() {
        while (true){
            synchronized (shop){
                if(shop.getBaoziNum() >= 10){
                    try {
                        System.out.println("面包师:" + Thread.currentThread().getName()
                                + "发现包子满了,果断休息...");
                        shop.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    shop.setBaoziNum(shop.getBaoziNum()+1);
                    System.out.println("面包师:" + Thread.currentThread().getName()
                            + "生产了1个包子, 还有" + shop.getBaoziNum() + "个包子");
                    shop.notify();  //通知顾客, 可以买包子了
                }
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//顾客
public class Customer implements  Runnable {
    private BaoZiShop shop;
    public Customer(BaoZiShop shop) {
        this.shop = shop;
    }

    public void run() {
        while (true){
            synchronized (shop){
                if(shop.getBaoziNum() <= 0){
                    try {
                        System.out.println("顾客:" + Thread.currentThread().getName()
                                + "发现没包子了, 通知老板弄包子, 在等待...");
                        shop.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    shop.setBaoziNum(shop.getBaoziNum()-1);
                    System.out.println("顾客:" + Thread.currentThread().getName() +
                            " 买了1包子, 还有" + shop.getBaoziNum() + "个包子");
                    shop.notify();  //客户急,催老板赶紧弄
                }
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class App {

    public static void main(String[] args) {

        //同一个包子店
        BaoZiShop shop = new BaoZiShop();

        new Thread(new Baker(shop), "baker1").start();  //一个面包师
        //new Thread(new Baker(shop), "baker2").start();
        //new Thread(new Baker(shop), "baker3").start();
        // new Thread(new Baker(shop), "baker4").start();
        // new Thread(new Baker(shop), "baker5").start();
        // new Thread(new Baker(shop), "baker6").start();

        new Thread(new Customer(shop), "customer1").start(); //一个顾客
        //new Thread(new Customer(shop), "customer2").start();
        // new Thread(new Customer(shop), "customer3").start();
        // new Thread(new Customer(shop), "customer4").start();
        // new Thread(new Customer(shop), "customer5").start();
        // new Thread(new Customer(shop), "customer6").start();
    }
}

结果

面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子
面包师:baker1生产了1个包子, 还有1个包子
顾客:customer1 买了1包子, 还有0个包子

分析代码:
1>顾客买一个包子, 然后通过shop.notify方法通知面包师干活,如果包子不满足则休息(调用shop.wait方法)
2>面包师生产一个包子,然后通过shop.notify方法通知顾客过来消费,如果包子满10个,则休息(调用shop.wait方法)
3>不管是顾客还是面包师,休息或者还是活动,发起动作的都是共享资源shop对象。
4>代码执行的结果基本是生产与消费交替打印

根据1,2点,再结合第4点,可以看出notify/wait起到唤醒线程/通知线程等待的功能,还有第3点,为啥需要操作同一个共享资源shop对象呢?下面我们从源码里找答案。
先说明: object类的wait/notify 方法都是native方法, 需要额外下载jvm的源码才可以看到传送门:openJDK 下载, 打开源码:openjdk\hotspot\src\share\vm\runtime 目录下objectMonitor.cpp objectMonitor.hpp 2个文件, 这些属于c语言源码。

其中在objectMonitor.hpp文件中定义一个非常重要的对象ObjectMonitor对象

ObjectMonitor对象:

主要用来监视创立的Object , 说白了就是我们经常说的所对象在jvm中的具体化(c++的结构体, 单纯看做java对象就可以)

  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

上面对象结构体中有4个非常关键的属性
_owner:指向于当前持有锁的线程
_object:指向例子中的shop对象
_WaitSet:调用wait方法之后的线程会被挂入到这个队列
_EntryList:等待获取锁(那个shop对象锁)的线程,被挂入到这个队列。

注意:_WaitSet跟_EntryList 实现类是ObjectWaiter, 是一个双向链表。

调用wait方法

在objectMonitor.cpp 文件中,有个方法:

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
   ....
   ObjectWaiter node(Self);
   AddWaiter (&node) ;
   _waiters++;                
   exit (true, Self) ;                   
   Self->_ParkEvent->park () ;
   .....
}

去掉一些看不懂, 留下几行比较有用的操作
1>ObjectWaiter node(Self); java线程调用shop.wait方法, jvm马上当前线程进行封装成一个ObjectWaiter 对象
2>AddWaiter (&node) ; 接着将该对象添加到_WaitSet 队列中

inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  assert(node != NULL, "should not dequeue NULL node");
  assert(node->_prev == NULL, "node already in list");
  assert(node->_next == NULL, "node already in list");
  // put node at end of queue (circular doubly linked list)
  if (_WaitSet == NULL) {
    _WaitSet = node;
    node->_prev = node;
    node->_next = node;
  } else {
    ObjectWaiter* head = _WaitSet ;
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}

3>_waiters++; 队列的长度加1
4>exit (true, Self) 这里注意, 直接退出,这里表示马上释放锁。也就是当线程调用了wait方法之后, 马上释放对象锁。
5>Self->_ParkEvent->park () ; 表示停止,等待, 也就是挂起来, 等待被唤醒。

调用notify方法

在objectMonitor.cpp 文件中,有个方法:

void ObjectMonitor::notify(TRAPS) {
      .....
     ObjectWaiter * iterator = DequeueWaiter() ;
     ObjectWaiter * List = _EntryList ;
     //根据不同的Policy值, 执行不同设置, 但最终操作都是下面这步
     _EntryList = iterator;  //将唤醒的线程放置到获取锁的队列中

     ......
}

总结:调用wait方法之后, 会将线程挂起, 暂存到_waitSet队列中,然后释放锁。

调用了DequeueWaiter方法

inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
  // dequeue the very first waiter
  ObjectWaiter* waiter = _WaitSet;
  if (waiter) {
    DequeueSpecificWaiter(waiter);
  }
  return waiter;
}

再调用DequeueSpecificWaiter方法

inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) {
  ObjectWaiter* next = node->_next;
  if (next == node) {
    assert(node->_prev == node, "invariant check");
    _WaitSet = NULL;
  } else {
    ObjectWaiter* prev = node->_prev;
    assert(prev->_next == node, "invariant check");
    assert(next->_prev == node, "invariant check");
    next->_prev = prev;
    prev->_next = next;
    if (_WaitSet == node) {
      _WaitSet = next;
    }
  }
  node->_next = NULL;
  node->_prev = NULL;
}

总结:调用notify方法之后, 会从_waitSet队列中随机唤醒一个线程, 然后暂存到_entryList队列中, 参与cpu争夺。注意此时还没有释放锁。需要等notify方法调用方法执行完之后再释放。

回归到例子:

public class App {

    public static void main(String[] args) {

        //同一个包子店
        BaoZiShop shop = new BaoZiShop();

        new Thread(new Baker(shop), "baker1").start();  //一个面包师
        new Thread(new Baker(shop), "baker2").start();
        new Thread(new Baker(shop), "baker3").start();
        new Thread(new Baker(shop), "baker4").start();
        new Thread(new Baker(shop), "baker5").start();
        new Thread(new Baker(shop), "baker6").start();

        new Thread(new Customer(shop), "customer1").start(); //一个顾客
        new Thread(new Customer(shop), "customer2").start();
        new Thread(new Customer(shop), "customer3").start();
        new Thread(new Customer(shop), "customer4").start();
        new Thread(new Customer(shop), "customer5").start();
        new Thread(new Customer(shop), "customer6").start();
    }
}

如果面包师与顾客是多人, 如果单纯使用wait /notify配对, 会存在死锁问题。原因:可能存在极端情况, 所有的面包师跟所有的客户都wait挂起,形成死锁。
解决:使用wait/notifyAll配置

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值