java线程间的通信

一、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方法在单线程中的具体应用

package com.kafka.testThread;

import java.util.LinkedList;

public class EventQueue {
    private final int max;

    static class Event{

    }

    private final LinkedList<Event>eventQueue = new LinkedList<>();

    private final static int DEFAULT_MAX_EVENT = 10;

    public EventQueue(){
    this(DEFAULT_MAX_EVENT);
    }

    public EventQueue(int max){
        this.max = max;
    }

    public void offer(Event event){
        synchronized (eventQueue){ //对象锁,和执行wait和notify方法的对象一致
            if(eventQueue.size() >= max){
                try {
                    console("the queue is full.");
                    eventQueue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            console(" the new event is submitted");
            eventQueue.addLast(event);
            eventQueue.notify();//唤醒阻塞的线程,告诉take线程可以往外取数据了
        }
    }

    public Event take() throws InterruptedException {
        synchronized (eventQueue){
            if(eventQueue.isEmpty()){
                console(" the queue is empty.");
                eventQueue.wait();
            }
            Event event = eventQueue.removeFirst();
            this.eventQueue.notify();//唤醒线程可以添加数据了
            console(" the event "+ event + "is handled.");
            return event;
        }
    }

    private void console(String message){
        System.out.println(Thread.currentThread().getName()+":"+message);
    }



}
package com.kafka.testThread;

import java.util.concurrent.TimeUnit;

public class EventClient {
    public static void main(String[] args) {
        final EventQueue eventQueue = new EventQueue();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(; ;){
                    eventQueue.offer(new EventQueue.Event());
                }
            }
        },"Producer").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for(; ;){
                    try {
                        eventQueue.take();
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"Consumer").start();
    }
}

以上程序模拟了生产者和消费者模式下,向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的块下来使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值