阻塞队列案例及部分源码

目录

ArrayBlockingQueue 

ArrayBlockingQueue的部分源码

LinkedBlockingQueue 

 LinkedBlockingDeque的部分源码

使用实例

PriorityBlockingQueue 

PriorityBlockingQueue的部分源码

 使用实例

 DelayQueue

 DelayQueue的源码

使用实例 


点击这里可以查看理论部分

注意:BlockingQueue 不接受null 元素。试图add、put 或offer 一个null 元素时,某些实现会抛出NullPointerException。null 被用作指示poll 操作失败的警戒值。 

ArrayBlockingQueue 

  • 一个由数组支持的有界的阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。
  • 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
  • 此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。

ArrayBlockingQueue的部分源码

  • 理解一下ArrayBlockingQueue的实现原理和机制

public class ArrayBlockingQueue <E> extends AbstractQueue<E>

        implements BlockingQueue<E>, java.io.Serializable {

    //数组的储存结构

    final Object[] items;    

   //锁采用的机制

    final ReentrantLock lock;

    public ArrayBlockingQueue( int capacity, boolean fair) {

        if (capacity <= 0)

            throw new IllegalArgumentException();

        this.items = new Object[capacity];

        //通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程

        lock = new ReentrantLock(fair);

        notEmpty = lock .newCondition();

        notFull =  lock .newCondition();

    }

    public boolean offer(E e) {

        checkNotNull(e);

        //使用ReentrantLock 锁机制

        final ReentrantLock lock = this.lock;

        lock.lock();//加锁

        try {

            if (count == items.length)

                return false ;

            else {

                enqueue(e);

                return true ;

            }

        } finally {

            lock.unlock();//释放锁

        }

    }

    private void enqueue(E x) {

        final Object[] items = this.items;

        items[ putIndex] = x;//通过数组进行储存

        if (++putIndex == items.length)

            putIndex = 0;

        count++;

        notEmpty.signal();

    }

…….

}

  • 实例

public class BlockingQueueTest {

       public static void main(String[] args) throws Exception {

             // 新建一个等待队列

             final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(16);

             // 四个线程

             for (int i = 0; i < 4; i++) {

                   //采用take方式取数据,会阻塞

                   new Thread(new Runnable() {

                         @Override

                         public void run() {

                               while (true ) {

                                     try {

                                          String log = (String) bq.take();

                                           parseLog(log);

                                    } catch (Exception e) {

                                    }

                              }

                        }

                  }).start();

            }

             for (int i = 0; i < 16; i++) {

                  String log = (i + 1) + ” –>  “;

                  bq.put(log); // 将数据存到队列里!

            }

      }

       // parseLog方法内部的代码不能改动

       public static void parseLog(String log) {

            System. out.println(log + System.currentTimeMillis());

             try {

                  Thread. sleep(1000);

            } catch (InterruptedException e) {

                  e.printStackTrace();

            }

      }

}

LinkedBlockingQueue 

  • 基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列 中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;
  • 只有当队列缓冲区达到最大值缓存容量时 (LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反 之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别 采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
  • 作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大 小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于 消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

 LinkedBlockingDeque的部分源码

public class LinkedBlockingDeque <E>

    extends AbstractQueue<E>

    implements BlockingDeque<E>, java.io.Serializable {

    final ReentrantLock lock = new ReentrantLock();//线程安全

    /**

     * @throws NullPointerException {@inheritDoc}

     */

    public boolean offerLast(E e) {

        if (e == nullthrow new NullPointerException();

        Node<E> node = new Node<E>(e);//每次插入后都将动态地创建链接节点

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            return linkLast(node);

        } finally {

            lock.unlock();

        }

    }

    public boolean offer(E e) {

        return offerLast(e);

    }

    public boolean add(E e) {

        addLast(e);

        return true ;

    }

    public void addLast(E e) {

        if (!offerLast(e))

            throw new IllegalStateException(“Deque full”);

    }

    public E removeFirst() {

        E x = pollFirst();

        if (x == nullthrow new NoSuchElementException();

        return x;

    }

    public E pollFirst() {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            return unlinkFirst();

        } finally {

            lock.unlock();

        }

    }

……

}

使用实例

 将ArrayBlockingQueue的例子换成LinkedBlockingQueue即可:

 // 新建一个等待队列

 final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(16);

换成:

final BlockingQueue<String> bq = new LinkedBlockingQueue<String>(16);

PriorityBlockingQueue 

  • 一个支持优先级排序的无界阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

PriorityBlockingQueue的部分源码

public class PriorityBlockingQueue <E> extends AbstractQueue<E>

    implements BlockingQueue<E>, java.io.Serializable {

    private final ReentrantLock lock ;//说明本类使用一个lock来同步读写等操作

    private transient Comparator<? super E> comparator;

     // 使用指定的初始容量创建一个 PriorityBlockingQueue,并根据指定的比较器对其元素进行排序。

    public PriorityBlockingQueue( int initialCapacity,

                                 Comparator<? super E> comparator) {

        if (initialCapacity < 1)

            throw new IllegalArgumentException();

        this.lock = new ReentrantLock();

        this.notEmpty = lock.newCondition();

        this.comparator = comparator;

        this.queue = new Object[initialCapacity];

    }

     public E poll() {

        final ReentrantLock lock = this.lock;

        lock.lock();

        try {

            return dequeue();

        } finally {

            lock.unlock();

        }

    }

……

}

 使用实例

public class PriorityBlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        //虽然初始容量设为16,但是如果插入的数量大于16,仍可以往里面添加数据即不会阻塞生产者
        PriorityBlockingQueue<Student> blockingQueue = new PriorityBlockingQueue<Student>(16, ( s1, s2) ->{
            return s1.getId() - s2.getId() > 0 ? 1 : -1;
        });

        // 新建一个等待队列
        for (int i = 0; i < 20; i++) {
            Student student = new Student(i, "学生" + i, System.currentTimeMillis());
            blockingQueue.put(student); // 将数据存到队列里!
        }

         while (true){
            Student take = blockingQueue.take();
            System. out.println("bq.peek()===>" + take.getName());
        }
    }
}

 DelayQueue

  • 一个支持延时获取元素的使用优先级队列的实现的无界阻塞队列。队列中的元素必须实现Delayed接口和Comparable接口,也就是说DelayQueue里面的元素必须有public int compareTo( T o)和long getDelay(TimeUnit unit)方法存在,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将DelayQueue运用在以下应用场景:
  • 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
  • 定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。

 DelayQueue的源码

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>

    implements BlockingQueue<E> {

    private final transient ReentrantLock lock = new ReentrantLock();//安全锁机制

    private final PriorityQueue<E> q = new PriorityQueue<E>();//PriorityQueue来存取元素

    public E take() throws InterruptedException {

        final ReentrantLock lock = this.lock;

        lock.lockInterruptibly();

        try {

            for (;;) {

                E first = q.peek();

                if (first == null)

                    available.await();

                else {

                    //根据元素的Delay进行判断

                    long delay = first.getDelay(NANOSECONDS);

                    if (delay <= 0)

                        return q .poll();

                    first = null; // don’t retain ref while waiting

                    if (leader != null)

                       //没到时间阻塞等待

                        available.await();

                    else {

                        Thread thisThread = Thread. currentThread();

                        leader = thisThread;

                        try {

                            available.awaitNanos(delay);

                        } finally {

                            if (leader == thisThread)

                                leader = null ;

                        }

                    }

                }

            }

        } finally {

            if (leader == null && q.peek() != null)

                available.signal();

            lock.unlock();

        }

    }

……

}

使用实例

  • 运行类
public class DelayQueueTest {
    public static void main(String[] args) throws Exception {
        // 新建一个等待队列
        final DelayQueue<Student> bq = new DelayQueue<Student>();
        for (int i = 0; i < 5; i++) {
            Student student = new Student(i, "学生" + i, System.currentTimeMillis());
            bq.put(student); // 将数据存到队列里!
        }

        //说明:因为存取用法想类似,这里主要取数据的相关操作

        /**
         * take:从DelayQueue拿数据,如果没有数据阻塞死等,获取头部而且移除头部,从Student可以看到延迟时间设为了10秒
         *  按照优先级高的即id比较小的先执行;===>通过Student中的compareTo方法决定
         *      bq.peek()===>学生0 交卷,用时10
         *      bq.peek()===>学生1 交卷,用时10
         *      bq.peek()===>学生2 交卷,用时10
         *      bq.peek()===>学生3 交卷,用时10
         *      bq.peek()===>学生4 交卷,用时10
         *      下面程序一直在执行,并没有停止也没有打印结果
         */
       /* while (true){
            Student take = bq.take();
            System. out.println("bq.peek()===>" + take.getName());
        }*/

        /**
         * poll:从DelayQueue拿数据,如果没有数据返回null,获取头部而且移除头部
         *  按照优先级高的即id比较小的先执行;===>通过Student中的compareTo方法决定
         *      bq.peek()===>学生0 交卷,用时10
         *      bq.peek()===>学生1 交卷,用时10
         *      bq.peek()===>学生2 交卷,用时10
         *      bq.peek()===>学生3 交卷,用时10
         *      bq.peek()===>学生4 交卷,用时10
         *      null
         *      null
         *      程序一直在打印null
         */
        /*while (true){
            Student take = bq.poll();
            if (take == null){
                System.out.println("null");
            }else{
                System. out.println("bq.peek()===>" + take.getName());
            }
        }*/

        /**
         * poll:从DelayQueue拿数据,如果在规定的时间内没有数据可以自定义返回默认的数据,获取头部而且移除头部
         *  按照优先级高的即id比较小的先执行;===>通过Student中的compareTo方法决定
         *      bq.peek()===>学生 -1 交卷,用时-1
         *      bq.peek()===>学生 -1 交卷,用时-1
         *      bq.peek()===>学生 -1 交卷,用时-1
         *      bq.peek()===>学生 -1 交卷,用时-1
         *      bq.peek()===>学生 -1 交卷,用时-1
         *      ......
         *      bq.peek()===>学生0 交卷,用时10
         */
        /*while (true){
            Student take = bq.poll(2, TimeUnit.SECONDS);
            if (take == null){
                System.out.println("null");
            }else{
                System. out.println("bq.peek()===>" + take.getName());
            }
        }*/


        //获取但不移除此队列的头部;如果此队列为空,则返回 null。
        /*while (true){
            Student take = bq.peek();
            System. out.println("bq.peek()===>" + take.getName());
        }*/

        /**
         * 直接抛出异常,程序不会继续执行
         *      Exception in thread "main" java.util.NoSuchElementException
         * 	        at java.base/java.util.AbstractQueue.remove(AbstractQueue.java:117)
         */
        while (true){
            Student take = bq.remove();
            System. out.println("bq.peek()===>" + take.getName());
        }
    }
    
}
  •  实体类
public class Student implements Delayed { //必须实现Delayed接口
    private int id;
    private String name ;
    private long submitTime ;// 交卷时间
    private long workTime ;// 考试时间
    private long currentTimeMillis;
    public String getName() {
        return this .name + " 交卷,用时" + workTime;
    }
    public Student(int id, String name, long submitTime) {
        this.id = id;
        this.name = name;
        this.workTime = submitTime;
        //将两秒转为毫秒值
        this.submitTime =  submitTime + TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS);
        System.out.println(toString());
    }
    //必须实现getDelay方法
    public long getDelay(TimeUnit unit) {
        // 返回一个延迟时间
        return unit.convert(submitTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    //必须实现compareTo方法,根据student的id作为优先级
    public int compareTo(Delayed o) {
//       比较的方法
        Student that = (Student) o;
        return id > that.id ? -1 : 1;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值