欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 多线程等待/通知机制

程序员文章站 2022-05-04 18:19:19
...

Java多线程 等待/通知机制

1、什么是等待通知机制

(1)提出原因

如果某一线程通过while轮询机制来检测某一条件,轮询时间间隔很小,会更浪费CPU资源;如果轮询时间间隔很大,可能会取不到想要的数据,所以就需要一种机制来减少CPU的资源浪费,而且还可以在实现多个线程之间的通信,这就是wait/notify机制的由来。

(2) 什么是等待/通知机制

通俗的说就是:线程A要等待线程B发出通知才执行,这个时候线程A可以执行wait方法,等待线程B执行notify方法唤醒线程A

(3)几个重要实现方法

1、wait()方法:当前执行代码的线程进行等待;将当前线程置入"预执行队列中";并且wait所在的代码行出停止执行,直到接到通知或者被中断为止。(在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用该方法

2、notify()方法:该方法用来通知那些正在等待的线程,如果有多个线程在等待,则线程规划器会随便挑出一个wait状态的线程,对其发出通知notify,执行notify后,当前线程不会立马释放锁,而是等待执行notify()方法的线程将程序执行完,才会释放锁,wait的线程才能获得锁,没有得到锁的线程会继续阻塞在wait状态。
只能在同步方法或者同步代码块中调用该方法

3、notifyAll()方法:是所有处于wait状态的线程都别唤起,进入可运行状态。此时,优先级最高的线程最先执行,但也可能是随机执行,这取决于JVM的实现。

2、底层实现原理

理解原理之前先来了解一下java对象模型:

2.1 对象模型

(1)每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass(类信息),保存在方法区,用来在JVM层表示该Java类。

使用new创建一个对象的时候,JVM会在对内存中创建一个instanceOopDesc对象,这个对象中包含了两部分信息,对象头以及实例数据。

(2)对象头中包括两部分:

Mark Word 一些运行时数据,其中就包括和多线程相关的锁的信息。用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等

Klass Point 元数据其实维护的是指针,指向的是对象所属的类的instanceKlass。

2.2 Moniter(对象监视者)

(1)为了解决线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。这个机制的保障来源于监视锁Monitor。

(2)每一个Object对象中内置了一个Monitor对象。(对象头的MarkWord中的LockWord指向monitor的起始地址)

Monitor相当于一个许可证,线程拿到许可证即可以进行操作,没有拿到则需要阻塞等待。

ObjectMonitor中有几个关键属性:

_owner:指向持有ObjectMonitor对象的线程

_WaitSet:存放处于wait状态的线程队列

_EntryList:存放处于等待锁block状态的线程队列

_recursions:锁的重入次数

_count:用来记录该线程获取锁的次数

线程T等待对象锁:_EntryList中加入T
线程T获取对象锁:_EntryList移除T,_owner置为T,计数器_count+1
持有对象锁的线程调用wait():_owner置为T,计数器_count-1,_WaitSet中加入T,等待被唤醒。
Java 多线程等待/通知机制

2.3 原理

(1)多个线程需要满足同一个对象锁的条件,也就是说被用一个Monitor(监视者)监视者;
(2)持有锁的线程可以从 EntryList队列中 进入到执行状态;
(3)正在执行的线程调用了 wait() 方法后,进入到WaitSet 等待队列中,等待被唤醒;并释放带掉对象锁;
(4)正在执行的线程调用了 notify() 或者 notifyAll() 方法后,通知位于WaitSet队列中的线程,它将释放锁,(继续执行完后代码才真正释放锁)。
(5)被唤醒的线程继续执行。

3、实例:生产者消费者问题

代码演示多个生产者,多个消费者问题

食物类:

public class Value {
public static String value = “”;
}

生产者类:

package ReadAndWriter;

public class Producers implements Runnable{

    private Object lock;

    public Producers(Object lock){
        this.lock = lock;
    }
    public void setValue() {
        try {
            synchronized (lock) {
                while (!Value.value.equals("")) {
                    System.out.println("生产者 "
                            + Thread.currentThread().getName() + " 等待消费者消费食物");
                    lock.wait();
                }
                System.out.println("生产者 " + Thread.currentThread().getName()
                        + " 生产食物中");
                String value = System.currentTimeMillis() + "_"
                        + System.nanoTime();
                Value.value = value;
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        while (true){
            setValue();
        }
    }
}

消费者类:

package ReadAndWriter;


public class Customers implements Runnable{

    private Object lock;

    public Customers(Object lock){
        this.lock = lock;
    }
    public void getValue() {
        try {
            synchronized (lock) {
                while (Value.value.equals("")) {
                    System.out.println("消费者 "
                            + Thread.currentThread().getName() + "没有食物,等待中");
                    lock.wait();
                }
                System.out.println("消费者 " + Thread.currentThread().getName()
                        + "获得食物");
                Value.value = "";
                lock.notify();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        while (true){
            getValue();
        }
    }
}

测试类:

package ReadAndWriter;

public class Main {
    public static void main(String args[]){
        String lock = "lock";
        Producers p1 = new Producers(lock);
        Producers p2 = new Producers(lock);

        Customers c1 = new Customers(lock);
        Customers c2 = new Customers(lock);
        new Thread(p1).start();
        new Thread(p2).start();
        new Thread(c1).start();
        new Thread(c2).start();
    }
}

结果:
Java 多线程等待/通知机制
从结果可以看出:该线程出现了假死的状态。

3.1 线程假死状态

那么为什么会出现假死的状态呢?

如果这个时候创建多个生产者,多个消费者,如果连续唤醒的是同类线程,那么会出现假死状态,就是线程都处于waiting状态,因为notify随机唤醒一个线程,如果唤醒的同类的,那么就浪费了一次唤醒,如果这个时候无法再唤醒异类线程,那么就会假死。

如图:
Java 多线程等待/通知机制

4、总结

本片主要就是介绍了什么是java 多线程的等待/通知机制,并简单的描述了实现的底层原理。

(1)等待/通知的实现必须是建立在同一个对象锁基础之上的;

(2)使用时需要注意避免引起假死锁;