Java 多线程系列Ⅲ

2024-02-01 10:35:03 浏览数 (1)

一、初识 wait、notify、notifyAll

wait()notify()方法是用于处理多线程同步的关键方法之一。它们通常用于协调多个线程对共享资源的访问和修改。

有一个共享的缓冲区,生产者负责往缓冲区中添加数据,消费者负责从缓冲区中取出数据。当缓冲区已满时,生产者需要等待消费者从缓冲区中取出一些数据后再继续添加;当缓冲区为空时,消费者需要等待生产者往缓冲区中添加一些数据后再继续取出。

定义一个名为SharedBuffer的类,它代表共享的缓冲区。这个类有两个属性:一个是整型的bufferSize,表示缓冲区的大小;另一个是队列queue,用于存储数据。它还有两个方法:一个是put(),用于生产者往缓冲区中添加数据;另一个是get(),用于消费者从缓冲区中取出数据。

代码语言:javascript复制
import java.util.LinkedList;  
import java.util.Queue;  
  
public class SharedBuffer {  
    private int bufferSize;  
    private Queue<Integer> queue;  
  
    public SharedBuffer(int bufferSize) {  
        this.bufferSize = bufferSize;  
        this.queue = new LinkedList<>();  
    }  
  
    public synchronized void put(int data) throws InterruptedException {  
        while (queue.size() == bufferSize) {  
            // 当缓冲区已满时,调用wait()方法使当前线程等待,并释放对象锁  
            wait();  
        }  
        queue.offer(data);  
        // 唤醒正在等待从缓冲区中取数据的消费者线程  
        notifyAll();  
    }  
  
    public synchronized Integer get() throws InterruptedException {  
        while (queue.isEmpty()) {  
            // 当缓冲区为空时,调用wait()方法使当前线程等待,并释放对象锁  
            wait();  
        }  
        Integer data = queue.poll();  
        // 唤醒正在等待往缓冲区中添加数据的生产者线程  
        notifyAll();  
        return data;  
    }  
}

当生产者调用put()方法往缓冲区中添加数据时,如果缓冲区已满,它会调用wait()方法使当前线程等待,并释放对象锁。当消费者从缓冲区中取出一些数据后,它会调用notifyAll()方法唤醒正在等待的生产者线程。同样地,当消费者调用get()方法从缓冲区中取出数据时,如果缓冲区为空,它也会调用wait()方法使当前线程等待,并在生产者往缓冲区中添加一些数据后通过调用notifyAll()方法唤醒消费者线程。这样,生产者和消费者就可以在多线程环境下安全地共享缓冲区了。需要注意的是,在这个例子中,我们使用了synchronized关键字来确保同一时刻只有一个线程可以执行put()get()方法。这是因为这两个方法都需要访问和修改共享资源(即缓冲区),所以需要进行同步处理以避免数据竞争和不一致的问题。

二、wait、notify、notifyAll 功能介绍

wait()

Wait() 方法使当前线程等待,并释放对象锁,直到其他线程调用该对象的notify()或notifyAll()方法唤醒该线程。它通常用于让当前线程等待,直到共享资源可用或满足某些条件。使用wait()方法时,需要先获取对象锁,否则会抛出IllegalMonitorStateException异常。

wait等待结束条件:

  1. 其他线程调用该对象的 notify 方法
  2. wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间)
  3. 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常

notify()

Notify() 方法唤醒在该对象上等待的单个线程。它通常用于通知等待该对象的单个线程,共享资源已经可用或满足某些条件。使用notify()方法时,需要先获取对象锁,否则会抛出IllegalMonitorStateException异常。

notify注意事项:

  1. 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  2. 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  3. 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行 完,也就是退出同步代码块之后才会释放对象锁。

notify all()

Notify All() 方法唤醒在该对象上等待的所有线程。它通常用于通知等待该对象的所有线程,共享资源已经可用或满足某些条件。使用notifyAll()方法时,需要先获取对象锁,否则会抛出IllegalMonitorStateException异常。

notifyAll注意事项:

虽然是同时唤醒 多个线程, 但是这 多个线程需要竞争锁,所以并不是同时执行,而仍然是先获取锁的先执行。

wait、notify、notifyAll 要点总结

  1. 必须在同步方法或同步块中使用wait()、notify()或notifyAll()方法,否则会抛出IllegalMonitorStateException异常。
  2. wait()、notify()和notifyAll()方法是Object类的final方法,因此不能被重写。
  3. wait()、notify()和notifyAll()方法必须与synchronized关键字一起使用,以确保线程安全。
  4. 调用wait()、notify()或notifyAll()方法的线程必须拥有当前对象的锁,否则会抛出IllegalMonitorStateException异常。
  5. 应该避免在循环中反复调用wait()方法,因为这可能会导致死锁。应该使用带有条件的wait循环,以便在满足条件时退出等待。
  6. 应该小心使用notify()和notifyAll()方法,以避免意外的唤醒所有线程而没有处理异常情况。应该在满足条件时再调用这些方法。
  7. 在使用wait()、notify()和notifyAll()方法时,应该考虑使用Java的Lock和Condition接口,以提高灵活性和可读性。

wait/notify 使用示例

代码语言:javascript复制
public class SharedResource {  
    private int count = 0;  
    private Object lock = new Object();  
  
    public void increment() {  
        synchronized (lock) {  
            count  ;  
            System.out.println("Count is: "   count);  
            lock.notify(); // 唤醒一个等待线程  
        }  
    }  
  
    public void decrement() throws InterruptedException {  
        synchronized (lock) {  
            count--;  
            System.out.println("Count is: "   count);  
            lock.wait(); // 当前线程等待,直到其他线程调用notify或notifyAll唤醒  
        }  
    }  
}

有一个共享资源类SharedResource,其中有一个计数器count和一个锁对象lockincrement()方法会增加计数器的值,并使用notify()方法唤醒一个等待线程。decrement()方法会减少计数器的值,并使用wait()方法使当前线程等待,直到其他线程调用notify()notifyAll()方法唤醒。

三、wait、join、sleep 归纳

(1)wait wait()方法是 Object类 提供的实例方法,可以使线程进入等待状态,直到其他线程调用了该对象的notify()或notifyAll()方法。通常被用于线程间通信,如生产者-消费者模式中(后续介绍),消费者需要等待生产者通知有新数据可取。当线程调用 wait() 方法时,它会释放占据的锁,并且线程的状态为WAITING,直到notify()或notifyAll()方法被调用。

(2)join join() 方法是 Thread类 提供的方法静态方法,用于等待被调用线程执行完毕。在主线程中调用了子线程的join() 方法后,主线程会进入 WAITING 状态,直到子线程执行完毕才会继续执行。可以用来保证多个线程按照指定顺序执行。

(3)sleep sleep()方法也是 Thread类 提供的实例方法,它可以使当前线程暂停执行一段时间。当线程调用 sleep() 方法时,它不会释放锁,线程的状态为 TIMED_WAITING 。通常被用于控制程序执行的速度或时间,或常常在循环内部以等待某些条件的发生。

wait() 方法是用于线程间的通信,join() 方法是用于等待其他线程执行完毕,sleep() 方法是用于暂停当前线程的执行。在使用上, wait 需要搭配 synchronized 使用,sleep 和 join 则不需要。

0 人点赞