线程间的通信(包括生产者消费者模型)

/**
 * 线程间的通信
 * 1)synchronized加锁的线程的Object类的wait/notify/notifyAll
 * wait  调用某个对象的wait方法可以让当前线程阻塞,并且当前线程要拥有
 * 某个对象的monitor lock
 * notify 调用某个对象的notify方法能够唤醒一个正在等待这个对象monitor
 * lock的线程,如果有多个线程都在等待这个对象的monitor lock,这个方法
 * 只能够唤醒一个
 * notifyAll 调用某个对象的notifyAll方法能够唤醒所有正在等待这个对象
 * monitor lock的线程
 * 思考:这三个方法不是Thread类中声明的方法,而是Object类中声明的方法?
 *
 * wait作用:
 * 使得当前执行代码的线程进行等待,当前线程置入"预执行队列中",代码在wait()
 * 所在的代码处立即停止执行,同时释放当前线程所拥有的锁。直到接到通知或者被
 * 中断为止。wait使用时必须在同步代码块/同步方法中,如果使用时没有拥有锁则会
 * 抛出IllegalMonitorStateException异常
 *
 * notify作用:
 * notify使用时必须在同步代码块/同步方法中,如果使用时没有拥有锁则会抛出
 * IllegalMonitorStateException异常。该方法用来同质化哪些等待该对象的monitor
 * lock的其他线程,如果有多个线程等待,则线程规划器随机挑选一个wait状态的线程,
 * 对该线程发出通知,并且使得它去获取该对象的对象锁。需要注意的是,notify调用之后
 * 不会立即释放锁,呈wait状态的线程不会马上获取该对象的monitor lock,直到执行
 * notify()的线程将程序执行完,也就是说退出syncrhonized代码块后,当前线程才会释放锁
 *
 * notifyAll作用:
 * 阐述两个概念:
 * 锁池和等待池
 * 锁池:假设thread A已经拥有了某个对象的锁,如果其他的线程想要调用这个对象的synchronized方法或者代码块,这些线程会进入到该对象的锁池当中
 * 等待池:假设thread A调用某个对象的wait方法,线程A就会释放该对象的锁,这个线程进入到该对象的等待池中
 *
 * 课后练习:
 * 生产者消费者模型
 *
 * 开发班:
 * 2)ReentrantLock类加锁的线程的Condition类的await/signal/signalAll
 */
  • 课堂练习:
  • 有三个线程,分别A,B,C线程,需要线程交替打印ABCABCABC…打印10次
class MyObj{
    
    
    private int nextValue;

    public void setNextValue(int nextValue) {
    
    
        this.nextValue = nextValue;
    }

    public int getNextValue() {
    
    
        return nextValue;
    }
}
class TestThread extends Thread{
    
    
    private String[] ABC = {
    
    "A", "B", "C"};
    private int index; //当前线程的编号
    private MyObj obj; //三线程的通信对象

    public TestThread(int index, MyObj obj){
    
    
        this.index = index;
        this.obj = obj;
    }

    @Override
    public void run() {
    
    
        for (int i=0; i<10; i++){
    
    
            synchronized (obj){
    
    
                //循环判断是否是当前线程执行,下一个执行的位置与当前线程位置是否一致
                while(obj.getNextValue() != index){
    
    
                    try {
    
    
                        obj.wait();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }

                System.out.println(ABC[index]+" ");
                //设置下一个线程的编号
                obj.setNextValue((index+1)%3);
                //通知其他处于wait状态的线程
                obj.notifyAll();
            }
        }
    }
}


public class TestDemo13 {
    
    
    public static void main(String[] args) {
    
    
        MyObj obj = new MyObj();

        obj.setNextValue(0);
        new TestThread(0, obj).start();
        new TestThread(1, obj).start();
        new TestThread(2, obj).start();
    }
}

wait notify 用法

public class Test {
    
    
    public static void main(String[] args) {
    
    
        String lock = new String("test");
        //这里是内部类法创建线程的另一种形式
        Thread threadA = new Thread("A") {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    synchronized (lock) {
    
    
//                        三个通信方法的使用的前提都是由synchronzed锁的前提
                        lock.wait(); //让当前线程陷入阻塞 A线程陷入阻塞 立即释放锁
                        System.out.println("the current running thread is " + Thread.currentThread().getName());
                    }
                } catch (InterruptedException e) {
    
    
                    //响应中断
                    System.out.println("the thread has been interrupted and the state is " + Thread.currentThread().isInterrupted());
                }
            }
        };
        threadA.start();
        
        threadA.interrupt();

        new Thread("B") {
    
    
            @Override
            public void run() {
    
    
                synchronized (lock) {
    
    
                    System.out.println("开始notify time " + System.currentTimeMillis());
                    lock.notify(); //唤醒当前A线程 不会立即释放锁
                    System.out.println("结束notify time" + System.currentTimeMillis());
                }
            }
        }.start();
    }
}

未屏蔽interrupt的运行结果
在这里插入图片描述

屏蔽掉interrupt的运行结果
在这里插入图片描述

/**
 * 生产者消费者模型
 * 生产者生产商品(数据)到某个地方(队列),消费者从某个地方(队列)消费商品(数据)
 * 提供put()/take()方法,如果队列已满,阻塞put直到有空间可用;如果队列已空,
 * 阻塞take直到有数据可用
 *
 * wait/notify/notifyAll
 */
class BlockingQueue<E>{
    
    
    //存取数据
    private final LinkedList<E> queue = new LinkedList<>();
    //有界队列最大值
    private int max;

    private static final int DEFAULT_MAX = 10;

    public BlockingQueue(){
    
    
        this(DEFAULT_MAX);
    }

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

    //生产数据
    public void put(E value){
    
    
        //生产者生产数据
        // 原因是多个生产者对应多个消费者,对queue操作
        synchronized(queue){
    
    
            //如果队列已经满,不允许生产者继续生产,生产者阻塞
           while(queue.size() >= max){
    
    
                System.out.println(Thread.currentThread().getName() + ":queue is full!");
                try {
    
    
                    queue.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            //反之,生产者生产数据
            System.out.println(Thread.currentThread().getName()+": the new data has been produced");
            queue.addLast(value);
            //期望唤醒消费者线程,可以消费了
            queue.notifyAll(); //全部线程都唤醒,公平地竞争queue的monitor lock
        }
    }

    //消费数据
    public E take(){
    
    
        //消费者消费数据
        synchronized (queue){
    
    
            //如果队列已经空,不允许消费者消费数据,消费者阻塞
            while(queue.isEmpty()){
    
    
                System.out.println(Thread.currentThread().getName()+": the queue is empty!");
                try {
    
    
                    queue.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            //反之,消费者消费数据
            E result = queue.removeFirst();
            //期望唤醒生产者线程,可以生产了
            queue.notifyAll();

            System.out.println(Thread.currentThread().getName()+": the data "+result+" has been handled");
            return result;
        }
    }
}

public class ProducerAndConcumerTest {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> queue = new BlockingQueue<>();
        for(int i=0; i<3; i++){
    
    
            //生产者生产数据
            new Thread("Producer"+i){
    
    
                @Override
                public void run() {
    
    
                    while(true){
    
    
                        queue.put((int)(1+Math.random()*1000));
                    }
                }
            }.start();
        }

        for(int i=0; i<3; i++){
    
    
            //消费者消费数据
            new Thread("Consumer"+i){
    
    
                @Override
                public void run() {
    
    
                    while(true){
    
    
                        queue.take();
                        try {
    
    
                            TimeUnit.MILLISECONDS.sleep(100);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }
}

在多线程中判断用while 而不用if的原因
if -》 只判断一次
导致两个问题:1) queue中没有元素仍然调用removeFirst
2) queue中元素超过给定的阀值仍然会执行addLast

第一种情况举例:
threadA、threadB在执行take方法都陷入了阻塞,另外一个threadC在执行put之后唤醒其中一个在take方法中阻塞的threadA,threadA消费完数据之后唤醒threadB,这就导致queue中没有元素threadB仍然调用removeFirst方法
第二种情况举例:
threadA、threadB在执行put方法都陷入了阻塞,另外一个threadC在执行take之后唤醒其中一个在put方法中阻塞的threadA, threadA生产完数据之后唤醒threadB,这就导致绕开的阀值检查,调用addLast继续向队列中添加元素

在这里插入代码片

猜你喜欢

转载自blog.csdn.net/weixin_47198561/article/details/114196365