多线程的通知机制

Posted Melody袁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程的通知机制相关的知识,希望对你有一定的参考价值。

典型应用场景–阻塞队列

生产者(Productor)—放入元素
消费者(Consumer)—取出元素
生产者放元素时,队列满了,该怎么办?
消费者取元素时,队列空了,该怎么办?
没有采用阻塞队列之前采用一种轮询模式,定期去问(性能浪费。实时性低)。
因此出现了另一种模式,通知模式,留个联系方式,状态改变以后会有人找你。

java中如何提供通知模式的?

Object的三个普通方法:
wait();导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
notify();唤醒正在等待对象监视器的单个线程。
notifyAll();唤醒正在等待对象监视器的所有线程。

由于Obejct是java中所有类的祖先类,所以,所有类的对象,都具备这三个方法。

注意事项:

  1. 要在某个对象(o)上进行wait/notify/notifyAll,首先必须持有这个对象的锁


import java.util.concurrent.TimeUnit;

class MyThread extends Thread

    //    boolean condation = true;
    @Override
    public void run() 

            try 

                TimeUnit.SECONDS.sleep(5);
             catch (InterruptedException e) 
                e.printStackTrace();

            
            synchronized (ThreadDemo.o)
                ThreadDemo.o.notify();
            

    

public class ThreadDemo 

    static Object o = new Object();

    public static void main(String[] args) throws InterruptedException 
        MyThread thread = new MyThread();
        thread.start();

        System.out.println("准备开始wait");
        synchronized (o)
            o.wait();
        
        System.out.println("从wait中醒来");
    


  1. 先notify再wait可以收到之前的通知信号吗?
    无法收到,通知信号状态不保留。
  2. 在调用wait的线程真正放弃CPU之前隐含着一次释放锁的过程,释放o这个锁,而不是所有的锁。
    从wait返回之前,隐含着需要重新请求锁(o这个锁)。
package 阻塞队列.demo2;
/*
通过jconsole查看线程持有锁的状态
*/

import java.util.*;
public class ThreadDemo 
    public static void main(String[] args) throws InterruptedException 
        Scanner s = new Scanner(System.in);
        synchronized (s)
            s.nextLine();
            System.out.println(1);//等待输入,不输入没有任何返回
            s.wait(20_0000);
            System.out.println(2);
            s.nextLine();
            System.out.println(3);
        
    


Thread.sleep(5000)和o.wait(5000)的区别

根本区别:
语义:
sleep休眠5秒(保证一定能够休眠5秒)
wait等待通知,最多等5秒(不保证一定能休眠够5秒,一旦被通知了,可以立即醒来)
表象区别:
sleep的调用不需要持有锁
wait休眠过程中,会释放锁,但只会释放o这个对象锁,其他持有的锁不释放。

package 阻塞队列.demo2;


import java.util.*;
class Lock1


class Lock2


public class ThreadDemo2 
    public static void main(String[] args) throws InterruptedException 
        Object lock1 = new Lock1();
        Object lock2 = new Lock2();
        Scanner s = new Scanner(System.in);
        synchronized (lock1)
            synchronized (lock2)
                s.nextLine();  //lock1 & lock2
                System.out.println(1);
//                lock2.wait(5_0000);//lock1
                Thread.sleep(5_0000);//lock1 & lock2
                System.out.println(2);
                s.nextLine(); //lock1 & lock2
            
        

    


wait被唤醒的条件

  1. 有其他线程调用notify,并且wait的线程被选中唤醒(随机唤醒)
  2. 有其他线程调用notifyAll
  3. 有其他线程执行类似interrupt()操作,通知线程停止,并抛出InterruptedException
  4. wait(timeout)达到超时时间时也会被唤醒
  5. 在某些系统上,由于JVM实现的原因,可能会出现虚假唤醒(上述的四个条件都没有发生的情况下)

一般调用wait方法,我们总是期待着某些条件。
所以一般的编码规范都是把wait放入一个循环中,循环的条件就是对我们的期望条件进行判断。
从wait中醒来时,无法保证条件符合我们的预期了。



import java.util.concurrent.TimeUnit;

class MyThread extends Thread 
    @Override
    public void run() 
        // 模拟算的时间比较久
        try 
            TimeUnit.SECONDS.sleep(5);
         catch (InterruptedException exc) 
            exc.printStackTrace();
        

        synchronized (ThreadDemo.class) 
            ThreadDemo.result = 100;
            ThreadDemo.class.notify();
        
    


public class ThreadDemo 
    static long result = -1;

    public static void main(String[] args) throws InterruptedException 
        MyThread thread = new MyThread();
        thread.start();

        long r;
        // 等待子线程算完
        synchronized (ThreadDemo.class) 
            ThreadDemo.class.wait();
            r = result;
        
        System.out.println(r); // 100
    


leetcode1114 按序打印

保证操作都加锁并且加的是同一把锁


public class Foo 

private int step = 1;

    public Foo() 

    

    public void first(Runnable printFirst) throws InterruptedException 


        // printFirst.run() outputs "first". Do not change or remove this line.
        while(true)
            synchronized (this)
                if (step == 1)
                    break;
                
            
            Thread.yield();
        
        wait();


        printFirst.run();
        synchronized (this)
            step = 2;
        


    

    public void second(Runnable printSecond) throws InterruptedException 

        // printSecond.run() outputs "second". Do not change or remove this line.
        while(true)
            synchronized (this)
                if (step == 2)
                    break;
                
            
            Thread.yield();
        


        printSecond.run();
        synchronized (this)
            step = 3;
        

    

    public void third(Runnable printThird) throws InterruptedException 

        // printThird.run() outputs "third". Do not change or remove this line.
        while(true)
            synchronized (this)
                if (step == 3)
                    break;
                
            
            Thread.yield();
        


        printThird.run();
        synchronized (this)
            step = 4;
        

    

以上是关于多线程的通知机制的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程学习三(线程间通信)

Day826.Java多线程等待&通知机制 -Java 并发编程实战

Day826.Java多线程等待&通知机制 -Java 并发编程实战

Java多线程之三volatile与等待通知机制示例

Java并发编程(04):线程间通信,等待/通知机制

Java并发编程(04):线程间通信,等待/通知机制