文档章节

Java并发编程初级篇(十二):使用wait和notify生产者消费者问题

阿拉德大陆的魔法师
 阿拉德大陆的魔法师
发布于 2016/11/24 17:25
字数 1250
阅读 24
收藏 0

在这里我们模拟一个生产者消费者问题。定义一个缓冲区,生产者生产数据并存入缓冲区,消费者从缓冲区中消费数据。缓冲区有固定大小,当缓冲区达到最大时生产者被挂起并等待消费者消费数据后再尝试将生产的数据加入缓冲区;当缓冲区数据量为0时,消费者被挂起直到有生产者向缓冲区中存入数据。

我们可以看到这个缓冲区是一个公共变量,所以缓冲区中数据的存放和取出都必须放置在一段synchronized修饰的同步代码中。

Java API的Object类提供了一组方法wait(),notify()和nofityAll()用于实现这个例子。

示例代码:

首先我们创建一个类来模拟实现一个阻塞数据缓冲池,这个缓冲池有一个链表结构用于缓冲数据,有一个整形变量用于定义数据缓冲区大小。一个set()方法用于模拟生产者向缓冲区内加入数据,一旦数据缓冲区满则挂起,成功插入数据后唤起所有消费者线程。一个get()方法用于模拟消费者从缓冲区中消费数据,一旦数据缓冲区空了则挂起,成功消费数据后唤起所有生产者线程。

public class EventStorage {
    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<Date>();
    }

    public void set() {
        synchronized (Main.producerController) {
            while (storage.size() == maxSize) {
                try {
                    Main.producerController.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            storage.add(new Date());
            System.out.printf("%s: Add one. storge size: %d.\n", Thread.currentThread().getName(), storage.size());
        }

        synchronized (Main.consumerController) {
            Main.consumerController.notifyAll();
        }
    }

    public void get() {
        synchronized (Main.consumerController) {
            while (storage.size() == 0) {
                try {
                    Main.consumerController.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.printf("%s: Get one %s. Storge size: %d.\n", Thread.currentThread().getName(), storage.poll(), storage.size());
        }

        synchronized (Main.producerController) {
            Main.producerController.notifyAll();
        }
    }
}

创建两个线程类用于模拟生产者和消费者。

public class Producer implements Runnable{
    private EventStorage eventStorage;

    public Producer(EventStorage eventStorage) {
        this.eventStorage = eventStorage;
    }

    @Override
    public void run() {
        eventStorage.set();
    }
}
public class Consumer implements Runnable{
    private EventStorage eventStorage;

    public Consumer(EventStorage eventStorage) {
        this.eventStorage = eventStorage;
    }

    @Override
    public void run() {
        eventStorage.get();
    }
}

定义主方法类,在这里我们启动20个消费者线程与20个生产者线程,其中生产者速度快,消费者速度慢。这样会导致生产者线程阻塞,等待消费者消费数据后被唤醒。

public class Main {
    public static final Object producerController = new Object();
    public static final Object consumerController = new Object();

    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();

        Producer producer = new Producer(eventStorage);
        Consumer consumer = new Consumer(eventStorage);

        Thread[] threads1 = new Thread[20];
        Thread[] threads2 = new Thread[20];

        for (int i = 0; i < 20; i++) {
            threads1[i] = new Thread(producer, "Producer-" + i);
            threads2[i] = new Thread(consumer, "Consumer-" + i);
        }

        for (int i = 0; i < 20; i++) {
            threads1[i].start();
            threads2[i].start();
        }
    }
}

查看控制台日日志,你会发现生产者快速加入10个数据到缓冲区,然后线程开始挂起,直到消费者开始消费数据,每次消费数据后生产者会被唤醒一次,并加入数据到缓冲区。最后20个生产者线程都执行完毕后,消费者线程才执行完毕。如果你想模拟消费者被阻塞只需要调整生产者速度慢于消费者速度。

Producer-0: Add one. storge size: 1.
Producer-19: Add one. storge size: 2.
Producer-18: Add one. storge size: 3.
Producer-17: Add one. storge size: 4.
Producer-16: Add one. storge size: 5.
Producer-15: Add one. storge size: 6.
Producer-14: Add one. storge size: 7.
Producer-13: Add one. storge size: 8.
Producer-12: Add one. storge size: 9.
Producer-11: Add one. storge size: 10.
Consumer-3: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-1: Add one. storge size: 10.
Consumer-19: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-10: Add one. storge size: 10.
Consumer-18: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-2: Add one. storge size: 10.
Consumer-17: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-9: Add one. storge size: 10.
Consumer-16: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-3: Add one. storge size: 10.
Consumer-15: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-8: Add one. storge size: 10.
Consumer-14: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-4: Add one. storge size: 10.
Consumer-13: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-7: Add one. storge size: 10.
Consumer-12: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-5: Add one. storge size: 10.
Consumer-11: Get one Thu Nov 24 17:12:13 CST 2016. Storge size: 9.
Producer-6: Add one. storge size: 10.
Consumer-10: Get one Thu Nov 24 17:12:14 CST 2016. Storge size: 9.
Consumer-9: Get one Thu Nov 24 17:12:15 CST 2016. Storge size: 8.
Consumer-8: Get one Thu Nov 24 17:12:16 CST 2016. Storge size: 7.
Consumer-7: Get one Thu Nov 24 17:12:17 CST 2016. Storge size: 6.
Consumer-6: Get one Thu Nov 24 17:12:18 CST 2016. Storge size: 5.
Consumer-5: Get one Thu Nov 24 17:12:19 CST 2016. Storge size: 4.
Consumer-4: Get one Thu Nov 24 17:12:20 CST 2016. Storge size: 3.
Consumer-2: Get one Thu Nov 24 17:12:21 CST 2016. Storge size: 2.
Consumer-1: Get one Thu Nov 24 17:12:22 CST 2016. Storge size: 1.
Consumer-0: Get one Thu Nov 24 17:12:23 CST 2016. Storge size: 0.

 

© 著作权归作者所有

共有 人打赏支持
阿拉德大陆的魔法师
粉丝 25
博文 91
码字总数 83019
作品 0
西城
程序员
私信 提问
Java并发编程初级篇(十六):Lock+Condition实现生产者消费者问题

之前我们在“Java并发编程初级篇(十二):使用wait和notify生产者消费者问题”,已经使用Java提供的synchronized关键字和wait(),notify(),notifyAll()方法实现过来生产者消费者问题。Java ...

阿拉德大陆的魔法师
2016/11/25
18
0
java中高级大公司多线程面试题

1)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编...

java成功之路
10/30
0
0
【转】Java线程面试题Top50

目录(?)[-] 50道Java线程面试题 1 什么是线程 2 线程和进程有什么区别 3 如何在Java中实现线程 4 用Runnable还是Thread 6 Thread 类中的start 和 run 方法有什么区别 7 Java中Runnable和Cal...

gehui
2015/08/14
0
0
Java线程面试题 Top 50

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者...

loda0128
2015/05/29
0
0
安卓中高级开发工程师面试之——面试永远逃不掉的Java线程面试题

不管你是Java工程师还是安卓开发工程师,只要你是计算机开发工程师,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大...

小饼干的梦
10/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何在Chrome浏览器中启动deviceready事件(尝试调试phonegap项目)?

我正在开发PhoneGap应用程序,我希望能够在Chrome中调试它,而不是在电话上调试。但是,我在onGetReady()函数中初始化我的代码,该函数在PhoneGap触发“deviceready”事件时触发。由于Chr...

kisshua
27分钟前
3
0
支付宝客户端架构分析:自动化日志收集及分析

摘要: 《支付宝客户端架构解析》系列将从支付宝客户端的架构设计方案入手,带领大家进一步了解支付宝在客户端架构上的迭代与优化历程。 小蚂蚁说: 《支付宝客户端架构解析》系列将从支付宝...

阿里云官方博客
30分钟前
1
0
nginx中部署vue打包后的静态文件

如何在nginx中部署静态资源就不描述了, 请看我的这篇博客 将vue脚手架项目打包后的静态文件放到nginx上, 发现有个问题, 即url上有#, 怎么去掉这个#呢. 1 项目中router的mode 路由的mode要为h...

克虏伯
48分钟前
8
0
JS容易理解错误的地方

在这端代码执行的末尾,你会不会hi变量回事函数中的hi了?你会不会认为这不是按引用传递了? 对值传递和引用传递产生质疑了? 1 var hi = {};2 function sayHello(hi) { ...

器石_
49分钟前
5
0
Java开发学习--MongoDB

之前只学过sql,第一次使用非关系型数据库。以前对于关系型数据库与非关系型数据库的概念很模糊,通过这次的学习对这两者有了一个清晰的概念。 主键 在MongoDB中,主键名叫"_id",如果在生成...

微笑向暖wx
53分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部