Java 多线程等待/通知机制

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,等待被唤醒。
在这里插入图片描述

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();
    }
}

结果:
在这里插入图片描述
从结果可以看出:该线程出现了假死的状态。

3.1 线程假死状态

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

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

如图:
在这里插入图片描述

4、总结

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

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

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

发布了24 篇原创文章 · 获赞 14 · 访问量 1965

猜你喜欢

转载自blog.csdn.net/qqq3117004957/article/details/104556396