Java并发编程实战1:java中的阻塞队列BlockingQueue

阻塞队列

最近在研究《Java并发编程实战》,里面有专门介绍阻塞队列BlockingQueue

阻塞队列提供了可阻塞的put和take方法:

  • 如果队列已经满了,put方法将阻塞直到有空间可用
  • 如果队列为空,那么take方法将会阻塞直到有元素可用

看一个例子

BlockingQueue是一个接口,先看一下ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。ArrayBlockingQueue使用时必须指定大小

先设置队列长度2,通过构造方法指定

put元素

put方法用于向队列尾部添加元素,队列大小为2,存储3个元素,会造成put第3个元素时线程阻塞

@Test
    public void testPut() throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
        blockingQueue.put("张三");
        blockingQueue.put("李四");
        blockingQueue.put("张三");
        System.out.println("添加了3个人");
    }

输出结果:

后面的"添加了3个人"一直不能输出,因为blockingqueue在put第2个元素"李四"之后就被阻塞了,后面的语句都不能执行

take元素

take方法用于从队尾获取元素,队列大小为2,存储了2个元素,会造成take第3个元素时线程阻塞

@Test
    public void testTake() throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
        blockingQueue.put("张三");
        blockingQueue.put("李四");

        for (int i = 0; i < 3; i++) {
            System.out.println(blockingQueue.take());
        }
        System.out.println("添加了3个人");
    }

多种存取方法比较

在前面,演示了put/take这种阻塞方法的使用,但是看下API,里面有好几组存取方法

添加元素

  • put:如果队列已经满了,put方法将阻塞直到有空间可用
  • add:如果队列已经满了,则返回异常
  • offer:如果元素添加成功,则返回true,否则返回false

put上面已经演示了,就不再赘述。

接着看下add:add方法在添加元素的时候,若超出了度列的长度会直接抛出异常:

@Test
    public void testAdd() throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
        blockingQueue.add("张三");
        blockingQueue.add("李四");
        blockingQueue.add("张三");
        System.out.println("添加了3个人");
    }

输出结果如下,说明这个方法是非阻塞的,直接返回Queue full:队列已满

再看下offer:offer方法在添加元素时,如果发现队列已满无法添加的话,会直接返回false

@Test
    public void testOffer() throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
        boolean of1 = blockingQueue.offer("张三");
        boolean of2 = blockingQueue.offer("李四");
        boolean of3 = blockingQueue.offer("张三");
        System.out.println("添加了3个人");
        System.out.println("元素是否获取成功:" + of1);
        System.out.println("元素是否获取成功:" + of2);
        System.out.println("元素是否获取成功:" + of3);
    }

输出:说明添加前2个元素时成功,返回true;到第3个元素满了,则添加失败,返回false

获取元素

  • take:如果队列为空,那么take方法将会阻塞直到有元素可用
  • poll:取走BlockingQueue里排在首位的对象,取不到时返回null

take上面已经演示了,就不再赘述。

接着看下poll:先在队列放入2个元素,再从里面取出3个元素,

@Test
    public void testPoll() throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(2);
        blockingQueue.put("张三");
        blockingQueue.put("李四");

        for (int i = 0; i < 3; i++) {
            System.out.println(blockingQueue.poll());
        }
        System.out.println("添加了3个人");
    }

输出:第2个元素没有,则返回null;这个方法还支持超时设置,若不能立即取出,则可以等time参数规定的时间,取不到时返回null

生产者-消费者

阻塞队列支持生产者-消费者这种设计模式。而BlockingQueue简化了生产者-消费者的实现过程。

先定义一个生产者线程ProducerDemo1,实现Runnable接口,通过BlockingQueue来存放生产的"商品"

public class ProducerDemo1 implements Runnable {

    private BlockingQueue<String> blockingQueue;

    public ProducerDemo1(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }
    
    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(1000);
                blockingQueue.put("商品");
                System.out.println(Thread.currentThread().getName() + "生产商品");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

现定义一个消费者线程ConsumerDemo1,实现Runnable接口,通过BlockingQueue消费"商品"

public class ConsumerDemo1 implements Runnable {

    private BlockingQueue<String> blockingQueue;

    public ConsumerDemo1(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }
    
    @Override
    public void run() {
        try {
            while (true) {
                TimeUnit.MILLISECONDS.sleep(1000);
                blockingQueue.take();
                System.out.println(Thread.currentThread().getName() + "消费商品");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

开始测试一下,生成2个线程来分别生产,消费商品

public class ProducerAndConsumerDemo1 {

    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(1);
        new Thread(new ProducerDemo1(blockingQueue)).start();
        new Thread(new ConsumerDemo1(blockingQueue)).start();
    }
}

输出:生产者,消费者交替输出(因为为了方便观察,队列只能存放一个元素)

线程安全问题?

通过生产者,消费者例子,不知道大家会不会有一个疑问,如果是多个线程同时生产,消费,会不会有并发问题呢?

答案是不会,看下put方法的源码,可以发现利用ReentrantLock加锁了,所以不会出现同步问题。take方法类似

/**
     * Inserts the specified element at the tail of this queue, waiting
     * for space to become available if the queue is full.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值