java线程间的通信

太过爱你忘了你带给我的痛 2021-12-12 00:19 384阅读 0赞

一、wait和notify方法详解

wait和notify方法并不是Thread特有的方法,而是Object中的方法,也就是说在JDK中的每个类都有这2个方法。

1.下面是wait方法的三个重载方法:

public final void wait() throws InterruptedException

public final void wait(long timeout) throws InterruptedException

public final void wait(long timeout,int nanos) throws InterruptedException

Object 的wait(long timeout)方法会导致当前线程进入阻塞,直到有其他线程调用了Object的notify或者notifyAll方法才能将其唤醒。

wait方法必须拥有该对象的monitor,也就是wait方法必须要在同步方法中使用。

当前线程执行了对象的wait方法之后,将会放弃对该monitor的所有权。并且 进入与该对象关联的wait set中,也就是说一旦线程执行了某个对象的wait方法之后,它就会释放对该对象的monitor的所有权,其他线程会继续争抢该monitord的所有权。

2.notify方法

唤醒单个正在执行该对象wait方法的线程

如果有某个线程由于执行该对象的wait方法而进入了阻塞则会被唤醒,如果没有则会被忽略。

被唤醒的线程需要重新获取对该对象所关联monitor的lock才能继续执行。

3.关于wait和notify的注意事项

wait方法是可中断方法,这也就意味着当前线程一旦调用了wait方法进入阻塞状态,其他线程是可以使用interrupt方法将其打断的;可中断方法被打断后会收到中断异常InterruptedException,同时interrupt标识也会被擦除。

线程执行了某个对象的wait方法之后,会加入与之对应的wait set中,每一个对象的monitor都会有一个与之关联的wait set.

当线程进入wait set后,notify方法可以将其唤醒,也就是从wait set中弹出,同时中断wait中的线程也会将其唤醒。

必须在同步方法中使用wait和notify方法,因为执行wait和notify的前提条件是必须有同步方法的monitor的所有权。

同步代码的monitor必须与执行wait notify方法的对象一致,简单地说就是用哪个对象的monitor进行同步,就只能用哪个对象进行wait和notify的操作。

4、下面是一个wait和notify方法在单线程中的具体应用

  1. package com.kafka.testThread;
  2. import java.util.LinkedList;
  3. public class EventQueue {
  4. private final int max;
  5. static class Event{
  6. }
  7. private final LinkedList<Event>eventQueue = new LinkedList<>();
  8. private final static int DEFAULT_MAX_EVENT = 10;
  9. public EventQueue(){
  10. this(DEFAULT_MAX_EVENT);
  11. }
  12. public EventQueue(int max){
  13. this.max = max;
  14. }
  15. public void offer(Event event){
  16. synchronized (eventQueue){ //对象锁,和执行wait和notify方法的对象一致
  17. if(eventQueue.size() >= max){
  18. try {
  19. console("the queue is full.");
  20. eventQueue.wait();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. console(" the new event is submitted");
  26. eventQueue.addLast(event);
  27. eventQueue.notify();//唤醒阻塞的线程,告诉take线程可以往外取数据了
  28. }
  29. }
  30. public Event take() throws InterruptedException {
  31. synchronized (eventQueue){
  32. if(eventQueue.isEmpty()){
  33. console(" the queue is empty.");
  34. eventQueue.wait();
  35. }
  36. Event event = eventQueue.removeFirst();
  37. this.eventQueue.notify();//唤醒线程可以添加数据了
  38. console(" the event "+ event + "is handled.");
  39. return event;
  40. }
  41. }
  42. private void console(String message){
  43. System.out.println(Thread.currentThread().getName()+":"+message);
  44. }
  45. }
  46. package com.kafka.testThread;
  47. import java.util.concurrent.TimeUnit;
  48. public class EventClient {
  49. public static void main(String[] args) {
  50. final EventQueue eventQueue = new EventQueue();
  51. new Thread(new Runnable() {
  52. @Override
  53. public void run() {
  54. for(; ;){
  55. eventQueue.offer(new EventQueue.Event());
  56. }
  57. }
  58. },"Producer").start();
  59. new Thread(new Runnable() {
  60. @Override
  61. public void run() {
  62. for(; ;){
  63. try {
  64. eventQueue.take();
  65. TimeUnit.MILLISECONDS.sleep(10);
  66. } catch (InterruptedException e) {
  67. e.printStackTrace();
  68. }
  69. }
  70. }
  71. },"Consumer").start();
  72. }
  73. }

以上程序模拟了生产者和消费者模式下,向queue中添加和取数的过程,过程中生产者提交客户端几乎没有延迟,而消费端,模拟带有一定的延迟。

5、多线程间通信要注意的事项

在4中,我们模拟了单线程下wait和notify的使用,如果多个线程同时进行take或者offer,那么上面的程序就会出现问题。

在多线程操作时,就不能使用notify方法了,要用notifyAll方法。notifyAll方法是同时唤醒所有的阻塞线程,同样被唤醒的线程仍需要继续争抢monitor的锁。

多线程下会出现以下2个问题:

notify方法只能唤醒一个线程,在上面代码中,虽然使用了同步关键字,进行数据同步,依旧会出现数据不一致的问题。假设EvenQueue中的元素为空,两个线程在执行take方法时,分别调用wait方法进入到了阻塞之中,另外一个offer线程执行addList方法后,向queue中添加了一个元素后,唤醒了其中一个阻塞的线程take,该线程顺利消费了一个元素之后恰巧再次唤醒了一个take线程(注意此时再次唤醒又是一个take线程,因为是同一个对象执行的notify方法,不能区分是哪个线程的唤醒方法。)这时候就可能出现LinkedList为空了,仍执行了removeFirst方法。

另外一种情况是:两个线程在执行offer方法的时候分别调用了wait方法而进入了阻塞中,即队列中达到了最大值之后,调用了阻塞方法wait,另外一个线程执行take方法消费了event元素并且唤醒了一个offer线程,而 该线程执行addList方法之后,queue中的元素为10,又唤醒了一个offer进程执行了addList方法,那么这个时候queue中的元素就超过了设定的max.

此时,针对在多线程下出现的以上2个问题就需要对程序做出修改:

1.把对是否进入阻塞状态判断的条件if方法改为while

2.对象的唤醒方法由notify改为notifyAll

个人理解:if改为while后,程序会循环判断当前线程是否满足进入wait阻塞状态的条件,当其中一个wait线程被唤醒后,第一次执行完while内程序后会再次去判断是否满足while里的条件。这样就会避免了上面那两个问题。

notify改为notifyAll,执行notifyAll后唤醒所有被wait阻塞的进程。让所有的阻塞线程都退出wait状态,重新去争抢同步锁。如果用notify只去唤醒一个线程,因为不能区分到底是生产者的线程还是消费者的线程,如果在生产者阻塞线程情况下,消费者消费完一个数据后,唤醒了一个生产者,生产者在queue里添加了一个元素后调用notify,此时的唤醒如果调用的一直是生产者的阻塞线程,那么程序就会陷入死锁状态。而我们设计生产者消费者模式的程序,在生产者生产了一个数据到queue后,再次唤醒一个线程是为了让消费者唤醒去消费,所以要用notifyAll方法去唤醒所有的wait线程,此时处于阻塞的线程执行完本轮循环后会由阻塞池转入到锁池中去,争抢锁资源。

6. sleep和wait

  • sleep :

sleep是Thread的静态方法,sleep方法不会释放lock,可以从任何上下文调用

  • wait:

wait是Object的实例方法,wait会释放锁,而且会加入到队列中等待唤醒,必须在synchronized的块下来使用

发表评论

表情:
评论列表 (有 0 条评论,384人围观)

还没有评论,来说两句吧...

相关阅读

    相关 线通信

    1、什么是多线程之间通信 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 于是我们引出了等待唤醒机制:

    相关 Java线通信

    Java是通过Object 类的wait() notify() notifyAll() 这几个方法实现线程间通信的。 wait():通知该线程进入睡眠状态,直到其他线程进入并

    相关 Java线通信

    上述例题无条件的阻塞了其他线程异步访问某个方法。Java对象中隐式管程的应用是很强大的,但是你可以通过进程间通信达到更微妙的境界。这在Java中是尤为简单的。 像前面

    相关 线通信

      https://www.cnblogs.com/hapjin/p/5492619.html 通常可用把并行程序理解为一组相互独立的、能够发关和接收消息的组件,这也称为角

    相关 线通信

    典型例子就是生产者-消费者模式,仓库为空时,消费者无法从仓库调动产品,只能wait,直到仓库有产品时被notify;仓库满了时,生产者则暂时停止生产(wait),直到仓库有空被