1

阻塞队列

在阻塞队列的帮助下,许多同步问题都可以被公式化。阻塞队列是队列,当线程试图对空队列进行出列操作,或试图向满的队列中插入条目时,队列就会阻塞。直到其他线程向队列中放入数据时才可以移除,同样,直到其他线程从队列中移除条目之后才可以加入。通过使用 轮询或等待-通知机制可以实现阻塞队列。就轮询机制来说,读线程周期性的调用队列的get方法,直到队列的消息变为可用。至于等待-通知机制,读线程仅仅是等待队列对象,队列对象会在有条目时通知线程。

阻塞队列的特征

阻塞队列的典型特征可以概括如下:

  • 阻塞队列提供了方法来向其中添加条目。这些方法的调用都是阻塞调用的,其中条目的插入必须等待,直到队列的空间变为可用。
  • 队列提供了方法来从中删除条目,对这些方法的调用同样是阻塞调用的。调用者会等待条目被放入空队列
  • add和remove方法可以选择性地为它们的等待操作提供超时并可能被中断
  • put和take操作在单独的线程中实现,从而在两种类型的操作之间提供了良好的绝缘性
  • 不能像阻塞队列中插入null元素
  • 阻塞队列可能受容量的限制
  • 阻塞队列的实现是线程安全的,然而批量操作,比如addAll,没有必要一定原子地执行,
  • 阻塞队列在本质上不支持“关闭”或“停止”操作,这表示没有更多的条目可添加

LinkedBlockingQueue

LinkedBlockingQueue是通过将阻塞队列的最大容量变为可变,进而扩展了数据阻塞队列的概念。你仍然可以在指定容量已禁止过度扩容。如果不指定容量,默认值是最大的整数值。没有容量限制是由好处的,因为如果先飞着晚于预订时间选取条目,生产者无需等待。与基于数组的队列相同,重载的构造函数可以接受集合指定的初始值。这种队列比基于数组阻塞队列具有更高的吞吐量。但同时也有较少的可预见性。除了移除操作在线性时间运行之外,队列的大多数操作都是在常数时间内运行的。

PriorityBlockingQueue

PriorityBlockingQueue是无界队列,可以决定元素的优先顺序,优先级可以由元素的自然顺序或你提供的比较器来确定。依照优先级队列的顺序,视图插入不可比较的对象会导致ClassCastException异常。如果系统资源耗尽,虽然是无界队列,添加操作也会失败,

股票交易系统

package com.guo.chap18;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by guo on 17/2/2018.
 * 基于阻塞队列的股票交易服务器
 * 需求:
 *      1、允许交易者往队列中添加出售订单,也可以获取待办的订单
 *      2、在任何给定的时间,如果队列已满,交易者就不得不等待某个位置变为空
 *      3、购买者必须等待,直到队列中有出售订单可用。
 *      4、为了简化情形,假设买方总是必须购买全额数量的可供出售的股票,不可以部分购买。
 */
public class StockExchange {

    public static void main(String[] args) {
        System.out.printf("Hit Enter to terminate %n%n");
        //1、创建LinkedBlockingQueue实例,因为是无限容量,所以交易者可以把任何数量的订单放入队列中,
        //   如果使用ArrayBlockingQueue,那么将会限制每只股票拥有有限次数的交易。
        BlockingQueue<Integer> orderQueue = new LinkedBlockingQueue<>();
        //2、创建Seller卖家实例,Seller是Runnable的实现类。
        Seller seller = new Seller(orderQueue);
        //3、创建100个交易者实例,将自己出售的订单放入队列中,每个出售订单都将会有随机的交易量。
        Thread[] sellerThread = new Thread[100];
        for (int i = 0; i < 100; i++) {
             sellerThread[i] = new Thread(seller);
             sellerThread[i].start();
        }
        //4、创建100个买家实例,选取待售的订单
        Buyer buyer = new Buyer(orderQueue);
        Thread[] buyserThread = new Thread[100];
        for (int i = 0; i < 100; i++) {
            buyserThread[i] = new Thread(buyer);
            buyserThread[i].start();
        }
        try {
            //5、一旦创建生产者和消费者线程,他们会永远保持运行,将订单放入队列以及从队列中获取订单
            //   根据给定时间的负载情况,定期自我阻塞,终止应用程序的方法是用户在键盘上按下Enter键。
            while (System.in.read() != '\n');
        } catch (IOException e) {
            e.printStackTrace();
        }
        //6、main函数会中断所有正在运行的生产者和消费者线程,要求它们中指并退出
        System.out.println("Terminating");
        for (Thread t : sellerThread) {
            t.interrupt();
        }
        for (Thread t : buyserThread) {
            t.interrupt();
        }

    }
}

卖家和买家

/**
 * 卖家
 * Seller类实现了Runnable接口并提供了以OrderQueue作为参数的构造函数
 */
class  Seller implements Runnable {
    private BlockingQueue orderQueue;
    private boolean shutdownRequest = false;
    private static int id;
    public Seller(BlockingQueue orderQueue) {
        this.orderQueue = orderQueue;
    }
    @Override
    public void run() {
        while (shutdownRequest == false) {
            //1、在每一次迭代中,为每一次的交易量生产一个随机数
            Integer quantity = (int) (Math.random() * 100);
            try {
                //2、调用put方法,将订单放入队列中,这是阻塞调用,只有在队列容量有限的情况下,
                //    线程才需要等待队列中有出现空的位置
                orderQueue.put(quantity);
                //3、为了方便用户,在控制台打印销售订单的详细信息,以及用于放置销售订单的线程详细信息
                System.out.println("Sell order by" + Thread.currentThread().getName() + ": " + quantity);
            } catch (InterruptedException e) {
                //4、run方法将无限期的运行,定期的向队列中提交订单,通过调用interrupt方法,这个线程可以被另外一个线程中断。
                //   interrupt方法产生的InterruptException异常简单的将shutdownRequest标志设置为true,将导致run方法无限循环终止
                shutdownRequest = true;
            }
        }
    }
}

/**
 *  买家
 *  Buyer类实现了Runnable接口并提供了以OrderQueue作为参数的构造函数
 */
class Buyer implements Runnable{
    private BlockingQueue orderQueue;
    private boolean shutdownRequest = false;
    public Buyer(BlockingQueue orderQueue) {
        this.orderQueue = orderQueue;
    }
    @Override
    public void run() {
        while (shutdownRequest == false) {
            try {
                //1、run方法通过调用take方法,从队列的头部取出待办的交易,
                //   如果队列中没有可用的订单,take方法将阻塞,
                Integer quantity = ((Integer) orderQueue.take());
                //2、为了方便,打印订单和线程的详细信息
                System.out.println("Buy order by " + Thread.currentThread().getName() + ": " + quantity);
            } catch (InterruptedException e) {
                shutdownRequest = true;
            }
        }
    }
}

输出

...
Buy order by Thread-134: 48
Buy order by Thread-134: 83
Buy order by Thread-134: 2
Buy order by Thread-134: 52
Sell order byThread-86: 90
Sell order byThread-86: 19
Sell order byThread-86: 64
Sell order byThread-86: 83
Sell order byThread-86: 27
Buy order by Thread-163: 94
Buy order by Thread-163: 74
...

当在键盘上按下Enter键使,程序终止

在这个程序中,如果没有使用阻塞队列,访问非同步队列中放置的交易时会发生竞争,每个人都会尝试抢得低于当前市场价格出售的股票,多个交易者会选取统一订单,交易之间会很混乱,由于阻塞队列 确保了同步地访问队列,因此交易的完整性绝不会 受到损害。

在这个例子中使用了LinkedBlockingQueue,气死也可以基于优先级的队列,这样会自动按照交易的买价和卖价对交易进行排列,具有最好买价和最低卖价的订单总是排在队列的头部。要使用基于优先级的队列,需要提供适当的比较器。

说明

1、GitHub代码欢迎star。你们轻轻的一点,对我鼓励特大。

2、个人认为学习语言最好的方式就是模仿思考别人为什么这么写。结合栗子效果更好,也能记住知识点

3、只因为自己知识欠缺,语言组织能力不行,所以只能以这样方式记录。感觉效果很好。

4、本文基于《Java 7 编程高级进阶》所写,写的非常不错,建议大家去看看


guoxiaoxu
440 声望405 粉丝

越努力,越幸运