【线程】 线程并发协作(生产者/消费者模式)

一个生产线程一个消费线程

notify是唤醒一个等待线程,如果有多个,会随机唤醒一个,其余仍然处在等待状态。

notifyAll唤醒所有的等待线程,如果有多个,会随机一个能重新获取锁,其他的会进入等待锁的状态。

缓冲区类BufferLock 中,value是共享变量,isEmpty 是否为空的信号量,并将put()和get()声明为同步方法。

每次只能生产一个产品,消费完后再生产下一个,依次交替,这个例子中使用notify,读者可以替换为notifyAll,不影响执行结果:

public class BufferLock {
    
    
    private int value;
    /**
     * value是否为空的信令号量,可以理解存储产品的池子,容量只有1<br>
     * 为空表示池子空了,可以放入数据,不可以取数据;<br>
     * 非空表示池子满了,不可以放入数据,可以取数据
     */
    private boolean isEmpty = true;

    public synchronized void put(int i) {
    
    

        if (!isEmpty) {
    
    
            try {
    
    
                this.wait(); // 当value非空时等待

            } catch (InterruptedException e) {
    
    

            }
        }
        value = i; // 往池子放入产品
        System.out.println("Sender put :" + i);
        isEmpty = false; // 表明池子有产品了
        notify(); // 唤醒其他等待线程,此处用notify和notifyAll均可以,因为只有1个消费者和1个生产者,交替唤醒
    }

    public synchronized int get() {
    
    
        if (isEmpty) {
    
    
            try {
    
    
                this.wait(); // 当value空时等待
            } catch (InterruptedException e) {
    
    

            }
        }

        isEmpty = true; // 表明池子没有产品了
        System.out.println("Receiver get :" + value);
        notify(); // 唤醒其他等待线程,此处用notify和notifyAll均可以,因为只有1个消费者和1个生产者,交替唤醒
        return value;
    }

    public static void main(String[] args) {
    
    
        BufferLock buffer = new BufferLock();
        new Sender(buffer).start(); // 启动一个生产者线程
        new Receiver(buffer).start(); // 启动一个消费者线程
    }

}

class Sender extends Thread {
    
     // 发送线程类
    private BufferLock buffer;

    public Sender(BufferLock buffer) {
    
    
        this.buffer = buffer;

    }

    public void run() {
    
    
        for (int i = 0; i < 6; i++) {
    
     // 发送6个数字
            buffer.put(i);

        }
    }
}

class Receiver extends Thread {
    
     // 接收线程类
    private BufferLock buffer;

    public Receiver(BufferLock buffer) {
    
    
        this.buffer = buffer;
    }

    public void run() {
    
    
        for (int i = 0; i < 6; i++) {
    
     // 接收6次
            buffer.get();
        }
    }

}

执行结果:

Sender put :0       //生产一个
Receiver get :0     //消费一个
Sender put :1
Receiver get :1
Sender put :2
Receiver get :2
Sender put :3
Receiver get :3
Sender put :4
Receiver get :4
Sender put :5
Receiver get :5

在put()方法中,先检查信号量状态,如果池子满了,则阻塞。否则放入一个产品,通知被阻塞的消费者线程。

在get()方法中,先检查信号量状态,如果池子为空,则阻塞。否则取出一个产品,通知被阻塞的生产者线程。

生产线程发送6个数字,消费线程收到6个数字,并且发送和接收的值都能匹配上。

一个生产线程2个消费线程

我们改进一下上面的例子,首先我们准备一个生产线程和2个消费线程,前者共生产12个数字,我们期望能消费到全部的12个数字。

public class BufferLock2 {
    
    
    private int value;
    /**
     * value是否为空的信令号量,可以理解存储产品的池子,容量只有1<br>
     * 为空表示池子空了,可以放入数据,不可以取数据;<br>
     * 非空表示池子满了,不可以放入数据,可以取数据
     */
    private boolean isEmpty = true;

    public synchronized void put(int i) {
    
    

        while (!isEmpty) {
    
    
            try {
    
    
                this.wait(); // 当value非空时等待

            } catch (InterruptedException e) {
    
    

            }
        }
        value = i; // 往池子放入产品
        System.out.println("Sender put :" + i);
        isEmpty = false;
        notifyAll(); // 唤醒其他等待线程
    }

    public synchronized int get() {
    
    
        while (isEmpty) {
    
    
            try {
    
    
                this.wait(); // 当value空时等待
            } catch (InterruptedException e) {
    
    

            }
        }

        isEmpty = true;
        System.out.println("Receiver get :" + value);
        notifyAll(); // 唤醒其他等待线程
        return value;
    }

    public static void main(String[] args) {
    
    
        BufferLock2 buffer = new BufferLock2();
        new Sender2(buffer).start();
        new Receiver2(buffer).start();
        new Receiver2(buffer).start();
    }

}

class Sender2 extends Thread {
    
     // 发送线程类
    private BufferLock2 buffer;

    public Sender2(BufferLock2 buffer) {
    
    
        this.buffer = buffer;

    }

    public void run() {
    
    
        for (int i = 0; i < 12; i++) {
    
     // 发送12个数字
            buffer.put(i);
        }
    }
}

class Receiver2 extends Thread {
    
     // 接收线程类
    private BufferLock2 buffer;

    public Receiver2(BufferLock2 buffer) {
    
    
        this.buffer = buffer;
    }

    public void run() {
    
    
        for (int i = 0; i < 6; i++) {
    
     // 接收6次
            buffer.get();
        }
    }

}

执行结果:

Sender put :0      //依次交替
Receiver get :0
Sender put :1
Receiver get :1
Sender put :2
Receiver get :2
Sender put :3
Receiver get :3
Sender put :4
Receiver get :4
Sender put :5
Receiver get :5
Sender put :6
Receiver get :6
Sender put :7
Receiver get :7
Sender put :8
Receiver get :8
Sender put :9
Receiver get :9
Sender put :10
Receiver get :10
Sender put :11
Receiver get :11

我们对比下这2段代码,发现主要的区别:

  • while (!isEmpty)
    原因是现在是3个线程,使用notify或notifyAll的话,不一定是生产和消费交替,而有可能是生产线程>消费线程>消费线程,由于连续唤醒的是消费线程,导致连续消费了两次,即有的值被读了两遍;while 作用是当连续唤醒的是消费线程时,不断的wait,直至下一次是生产线程。

读者可以替换为if语句,执行结果可能为:

Sender put :0
Receiver get :0   //消费线程
Receiver get :0    //连续消费线程,同一个值被消费多遍
Receiver get :0
Sender put :1
Receiver get :1
Receiver get :1
Receiver get :1
Sender put :2
Receiver get :2
Receiver get :2
Receiver get :2
Sender put :3
Receiver get :3
Sender put :4
Receiver get :4
Sender put :5
Receiver get :5
Sender put :6

猜你喜欢

转载自blog.csdn.net/m0_45406092/article/details/108818927
今日推荐