多线程笔记5 wait notify 阻塞队列

object o = new object();
o.wait();
o.notify();
o.notifyAll();
wait方法和notify(意思为 “通知”)方法均属于object。

当 A线程调用o.wait()时,A会自动放弃CPU并且从Runnable状态变成Waiting状态,o指向对象有一个等待集,A会被调入等待集中。

当B线程调用o.notify();时,会随机唤醒一个等待集上的线程,而o.notifyAll();则会唤醒全部线程,对B本身无影响。

因为以上三个方法均对对象的等待集产生了修改,所以规定在用以上方法前,必须加锁。
synchronized(o){
o.wait() //wait内部会在线程放弃CPU之前将锁解开,当被唤醒后
} // 其会再次请求锁,并执行unlock
synchronized(o){
o.notify()
}
在这里插入图片描述

import java.util.Scanner;

/*
单生产者-单消费者情况

1. 是不是线程安全的?   是否有共享,是否有修改?
2. 怎么修改成线程安全的版本? 通过加锁解决
                              volatile 不可以解决问题
3. 生产者在队列满时等待-消费者在队列空时等待
4. 生产者需要唤醒可能在等消费者;消费者需要唤醒可能在等的生产者
*/
public class MyBlockingArrayQueue {
    int[] array = new int[10];  // 下标处的数据可能出现生产者和消费者修改同一处的情况
    int front = 0;  // 只有消费者修改 front
    int rear = 0;   // 只有生产者修改 rear
    int size = 0;   // size 是生产者消费者都会修改的

    // 生产者才会调用 put
    synchronized void put(int value) throws InterruptedException {
        // 考虑满的情况
        if (size == array.length) {
            // 队列已满
            //throw new RuntimeException("队列已满"); //满了
            wait(); //停
        }

        array[rear] = value;
        rear++;
        if (rear == array.length) {
            rear = 0;
        }
        //rear = (rear + 1) % array.length;
        size++;     // 我们需要保障的是 size++ 的原子性,所以 volatile 无法解决
        notify();
    }

    // 调用 take 的一定是消费者
    synchronized int take() throws InterruptedException {
        // 考虑空的情况
        if (size == 0) {
            // 空的
            //throw new RuntimeException("队列已空");
            wait();
        }

        int value = array[front];
        front++;
        if (front == array.length) {
            front = 0;
        }
        //front = (front + 1) % array.length;
        size--;
        notify();

        return value;
    }

    static MyBlockingArrayQueue queue = new MyBlockingArrayQueue();

    static class Producer extends Thread {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 100; i++) {
                    System.out.println("准备放入 " + i);
                    queue.put(i);
                    System.out.println("放入成功");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Producer producer = new Producer();
        producer.start(); //生产者启动

        Thread.sleep(2 * 1000);

        Scanner scanner = new Scanner(System.in);//消费者手动控制
        while (true) {
            scanner.nextLine();
            System.out.println(queue.take());
        }
    }
}

synchornized保证了拿的时候不能加,加的时候不能拿。
在take、put方法中的wait、notify,则保证了阻塞,满时加不进,空时取不出,一个线程wait时,只能等待另一个线程调用对应方法末尾的notify来唤醒。

当多对多时,可能会出现生产者唤醒生产者,消费者唤醒消费者的情况,此时会导致线程不安全,因为size在随机变,解决方法就是在线程醒来时再次判断wait的条件,也就是将if换成while。

多对多的还有种情况就是,所有消费者都在wait(队列空时一直都是消费者抢到cpu),然后生产者在队列加满后一直唤醒生产者,于是所有生产者也都在wait,导致整个程序停止下来。解决方法是将notify改成notifyAll。

java中的阻塞队列 ArrayBlockingQueue 顺序表实现
LinkedBlockingQueue 链表实现
PriorityBlockingQueue 堆实现

发布了15 篇原创文章 · 获赞 0 · 访问量 260

猜你喜欢

转载自blog.csdn.net/rushrush1/article/details/104878573