Java5线程并发库之可阻塞的队列

可阻塞的队列

下面我们开始来了解java5并发库提供的集合。首先我们来了解一个队列,这个队列就是一个数据的集合。就相当于一系列的单元格,我们可以往这个单元格里面逐一的放数据。我们取数据的时候是随机取一个呢,还是先取哪一个后取哪一个?我们取数据的时候是先取那个最先放进去的。就是先进先出就叫队列。这个队列呢,有固定大小的,也有不固定大小的,固定大小的当已经到了固定大小了,假如说,这个队列里面我放了8个数据,总共大小就是8,结果还一个都没取走,再要往里面放的时候就会阻塞,或者报错。就说,啊,放不进去了,或者就等待,苦苦等待。这就几种情况都会有。这是这个队列。如果是非阻塞队列,那就不阻塞,就直接报错。如果是阻塞队列,就会阻塞。队列又分为固定长度的,还有不固定长度的。

ArrayBlockingQueue

Java5提供的对阻塞队列的描述的类为ArrayBlockingQueue. 一个由数组支持的有界阻塞队列。一旦创建了这样的缓存区,就不能再增加其容量。这个类他实现了一个接口叫BlockingQueue.

BlockingQueue支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

 

抛出异常

特殊值

阻塞

超时

插入

add(e)

offer(e)

put(e)

offer(e, time, unit)

移除

remove()

poll()

take()

poll(time, unit)

检查

element()

peek()

不可用

不可用

add(e),offer(e),put(e)这3个方法都可以往队列里面加数据,假设这个队列的大小是8个空间,刚开始他是空的,我往他里面想加一个元素进去,请问,调用这三个当中的哪一个?调用这三个当中的任何一个都成。都可以。这三个方法的区别在于,当这个队列满了,它有8个空间,最后8个空间都装满了,这时候再用add的时候,装不进去了,它就会抛异常,而不是堵。调用offer呢,就是立马返回,如果放进去了,那就是true,没放进去它就不放了,它就告诉你,我放不进去,返回一个false.它也不报错。程序没报错,程序只是返回值为fale,表示放入失败。程序没有抛异常。这个方法你要检查放进去没有,你就检查是不是true.add方法要知道放进去没有,就是try catch。put方法呢,就是放不进去就堵在那里,阻塞在那里,直到空出一个单元格,立马就放进去。同样,取数据的方法也是一样的道理。如果一个都没有,取不到,remove方法抛异常,poll方法返回null,take方法阻塞在那里,直到人家放了一个,我就马上把放的那个拿走。

下面,我们通过代码来演示可阻塞队列的运行效果。我们这个代码是有两个线程,从队列里面取数据,有一个线程往队列里面放数据。如果放的比较快呢,你就可以看到那个放数据的线程堵着,如果取得比较快呢,你就可以看到取的那个线程阻塞。

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

public class BlockingQueueTest {

    public static void main(String[] args) {

        final BlockingQueue queue = new ArrayBlockingQueue(3);// 创建了一个数组队列,这个队列允许放3个数据

        for (int i = 0; i < 2; i++) {// 循环创建两个放数据的线程

            new Thread() {

                public void run() {

                    while (true) {

                        try {

                            Thread.sleep((long) (Math.random() * 1000));

                            System.out.println(Thread.currentThread().getName() + "准备放数据");

                            queue.put(1);//如果队列已经放满了,这句代码会造成线程阻塞

                            System.out.println(Thread.currentThread().getName() + "已经放了数据,队列目前有"

                                    + queue.size() + "个数据");

                        } catch (InterruptedException e) {

                            e.printStackTrace();

                        }

                    }

                }

            }.start();

        }

        new Thread() {// 取数据的那个线程

            public void run() {

                while (true) {

                    try {

                        // 将此处的睡眠时间分别修改为100和1000,观察运行结果

                        Thread.sleep(1000);

                        System.out.println(Thread.currentThread().getName() + "准备取数据");

                        queue.take();//如果队列取空了,这句代码会造成线程阻塞

                       //不能保证上面的take和下面的打印语句是原子性的,当执行完take,并不是马上执行打印,这中间会被其它线程打断

                        System.out.println(Thread.currentThread().getName() + "已经取走了数据,队列目前有" + queue.size()

                                + "个数据");

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

            }

        }.start();

    }

}

下面再看一个代码示例,用阻塞队列来实现同步通知的功能:首先来看一道题,首先有一个队列queue1,刚开始它是空的,空的我就想把数据放进去,我就调用put方法,放数据进去。然后有一个queue2队列,它一开始就是满的,它也去put,这时候它就阻塞。我那边queue它put完了以后,我让queue2调用take方法取数据,queue2中被取了一个数据后,它的put方法就不再阻塞了,代码继续向下运行,它下面的代码是让queue1调用take方法取数据….如下图所示:就这样,你一下,我一下,你通知我,我通知你,就是用阻塞队列也可以实现互斥,这个队列的单元格只有一个,把数据一放进去了以后,就不能继续放了,就只能等,除非我把数据拿走了,你才能走。

l  下面我们用代码来实现一下:用两个只有1个空间的队列来实现同步通知的功能。

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

public class BlockingQueueCommunication {

    /**

     * @param args

     */

    public static void main(String[] args) {

        final Business business = new Business();

        new Thread(new Runnable() {

            @Override

            public void run() {

                for (int i = 1; i <= 50; i++) {

                    business.sub(i);

                }

            }

        }).start();

        for (int i = 1; i <= 50; i++) {

            business.main(i);

        }

    }

    static class Business {

        BlockingQueue<Integer> queue1 = new ArrayBlockingQueue<Integer>(1);

        BlockingQueue<Integer> queue2 = new ArrayBlockingQueue<Integer>(1);

        {//匿名构造方法,在所有的构造方法之前执行

            try {

                queue2.put(1);//创建对象的时候就给queue2放满

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        public void sub(int i) {

            try {

                queue1.put(1);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            for (int j = 1; j <= 10; j++) {

                System.out.println("sub thread sequece of " + j + ",loop of " + i);

            }

            try {

                queue2.take();//直到它take之前main方法都堵在queue2.put(1);这句代码

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

        public void main(int i) {

            try {

                queue2.put(1);

            } catch (InterruptedException e1) {

                e1.printStackTrace();

            }

            for (int j = 1; j <= 100; j++) {

                System.out.println("main thread sequece of " + j + ",loop of " + i);

            }

            try {

                queue1.take();//直到它take之前,sub方法都堵在queue1.put(1);这句代码

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

SynchronousQueue

接下来我们再介绍一个同步的阻塞队列:SynchronousQueue

一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作。也就是说,我往队列里面去插入数据,插不插得进去?插不进去。哪怕队列是空的我也插不进去,一旦有一个人来读得时候,读不读得到?都不到,队列里面没有。但是,这个队列是,我已发现有人来读了,啪,我就放进去了。那个读得人啪,马上读走了。很像那个交换情况,有个人举着毒粉要跟人交换,换不换的出去?换不出去,只有那个来买毒粉的人到达了才换出去。也就是说,我想往队列里面插数据,插进去了吗?没有。就在那个读数据的人到达了,它在读数据的一刹那间,我插进去了,他读走了。之前那个Exchanger对象是单对单的交换数据,现在呢这个是可以有多个来抢数据。有人举着一包毒粉想插进去,结果,买毒粉的人没来,他能不能放到队列里面去呢?放不进去。结果,买毒粉的一下来了3个,他能不能放进去?能放进去。那谁拿走了呢?看谁抢的快!如果说他同时有3包白粉,同时都扔进去了,那就3个拿的人各抢到1包走了。大概呢是这么个意思。如果没有线程等着要拿,你就会阻塞,放不进去。如果有线程等着要拿,你就直接交给他。也就是这只手交那只手,中间并没有把东西存放到哪个第三方的存放点。暂时这么理解,具体还要研究源码才知道。

同步队列没有任何内部容量,甚至连一个队列的容量都没有。它非常适合于传递性设计,在这种设计中,在一个线程中运行的对象要将某些信息、事件或任务传递给在另一个线程中运行的对象,它就必须与该对象同步。

猜你喜欢

转载自my.oschina.net/u/3512041/blog/1822501