JUC线程框架深度解析 — 05、阻塞队列

     Queue是一个队列,特征是FIFO,如果要实现生产者与消费者模式,
可以采用队列来进行中间的缓冲处理,好处:生产者可以一直不停歇的生产数据。

    BlockingQueue是Queue子接口。所以它实现有队列的基本特征:

Public interface BlockingQueue<E> extends Queue<E>
    在最初利用Queue实现生产者与消费者模型时发现一个问题:所有的消费者可能不是一个个轮流操作,

而是有可能某一个消费者会长期进行消费处理。

【 阻塞队列 】

➤ BlockingQueue通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。


➤ 一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界
   点。也就是说,它是有限的。如果该阻塞队列达到了其临界点,负责生产的线程将会在
   往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中

   拿走一个对象。

BlockingQueue也是一个处理接口,如果要想操作BlockingQueue也需要使用它的一系列子类。


【 BlockingQueue基础子类 】
    对于阻塞队列而言最基础的两个实现子类:数组的队列、链表的队列。

范例:使用BlockingQueue实现一个生产者与消费者模型

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // 允许保存5个数据队列
        BlockingQueue<String> queue = 
                   new ArrayBlockingQueue<String>(5);
        for (int x = 0; x < 3; x++) {
            new Thread(() -> {
                for (int y = 0; y < 5; y++) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        String str = "【生产数据{"
                                + Thread.currentThread().getName()
                                + "}】y = " + y ;
                        queue.put(str);    // 会进入到生产的阻塞状态
                        System.out.println(str);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者-" + x).start();
        }
        for (int x = 0; x < 5; x++) {
            new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                        // 队列内容为空了
                        if (queue.isEmpty()) {
                            break; // 结束循环
                        }
                        System.out.println("【消费数据{"
                                + Thread.currentThread().getName()
                                + "}】" + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者-" + x).start();
        }
    }
}

     除了数组之外也可以使用链表来进行操作:LinkedBlockingQueue。在使用这个类进行BlockQueue接口对象实例化的时候,

如果没有设置容量,则容量为”Integer.MAX_VALUE”。

范例:修改为链表实现

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // 允许保存5个数据队列
        BlockingQueue<String> queue 
                        = new LinkedBlockingQueue<String>(5);
        for (int x = 0; x < 3; x++) {
            new Thread(() -> {
                for (int y = 0; y < 5; y++) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        String str = "【生产数据{"
                                + Thread.currentThread().getName()
                                + "}】y = " + y ;
                        queue.put(str);    // 会进入到生产的阻塞状态
                        System.out.println(str);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者-" + x).start();
        }
        for (int x = 0; x < 5; x++) {
            new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                        if (queue.isEmpty()) { // 队列内容为空了
                            break; // 结束循环
                        }
                        System.out.println("【消费数据{"
                                + Thread.currentThread().getName()
                                + "}】" + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者-" + x).start();
        }
    }
}

     链表是通过索引来进行弹出数据的,而链表只需要弹出第一个元素即可。

范例:采用优先级的PriorityBlockingQueue来实现数据操作

     采用了Comparable接口来进行处理实现。

import java.util.concurrent.BlockingQueue; PriorityBlockingQueue; TimeUnit;
public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // 允许保存5个数据队列
        BlockingQueue<String> queue = 
                     new PriorityBlockingQueue<String>(5);
        for (int x = 0; x < 3; x++) {
            new Thread(() -> {
                for (int y = 0; y < 5; y++) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        String str = "【生产数据{"
                                + Thread.currentThread().getName()
                                + "}】y = " + y ;
                        queue.put(str);    // 会进入到生产的阻塞状态
                        System.out.println(str);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者-" + x).start();
        }
        for (int x = 0; x < 5; x++) {
            new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                        if (queue.isEmpty()) { // 队列内容为空了
                            break; // 结束循环
                        }
                        System.out.println("【消费数据{"
                                + Thread.currentThread().getName()
                                + "}】" + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者-" + x).start();
        }   }  }

对于使用拿一个具体的子类完全是由你具体的开发环境来决定,需要至少知道BlockingQueue这个阻塞队列核心就是提供有同步和队列的功能。

【 同步队列:SynchronousQueue 】

     之前使用BlockingQueue每一次都可以保存多个数据对象信息,但是有些时候只能够允许你保存一个数据的信息,这种情况下就要使用SynchronousQueue子类完成。

范例:使用同步队列进行处理

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // 允许保存5个数据队列
        BlockingQueue<String> queue = 
                                    new SynchronousQueue<String>();
        for (int x = 0; x < 3; x++) {
            new Thread(() -> {
                for (int y = 0; y < 5; y++) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        String str = "【生产数据{"
                                + Thread.currentThread().getName()
                                + "}】y = " + y ;
                        queue.put(str);    // 会进入到生产的阻塞状态
                        System.out.println(str);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者-" + x).start();
        }
        for (int x = 0; x < 5; x++) {
            new Thread(() -> {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                        System.out.println("【消费数据{"
                                + Thread.currentThread().getName()
                                + "}】" + queue.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "消费者-" + x).start();
        }    }  }
现在不关心有多少个生产者和消费者都采用一个接一个的形式执行。

【 双端阻塞队列:BlockingDeque 】
       BlockingQueue主要特征是只能够从一方面获取数据,也就是说它按照了一个队列的形式采用了FIFO的处理完成,

但是现在有些操作者希望可以按照前后各自处理操作。

➢ 一个BlockingDeque线程在双端队列的两端都可以插入和提取元素。
➣ 一个线程生产元素,并把它们插入到队列的任意一端。如果双端队列已满,
   插入线程将被阻塞,直到一个移除线程从该队列中移除了一个元素。如果双端队列为空,

   移除线程将被阻塞,直到一个插入线程向该队列插入了一个新元素。


范例:观察双端阻塞队列的基本使用

public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        BlockingDeque<String> deque = 
                       new LinkedBlockingDeque<String>();
        for (int x = 0; x < 3; x++) {
            new Thread(() -> {
                for (int y = 0; y < 5; y++) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        String str = null ;
                        if (y % 2 == 0) {
                            str = "【[FIRST]生产数据{"
                          + Thread.currentThread().getName()
                                    + "}】y = " + y ;
                            deque.addFirst(str);
                        } else {
                            str = "【[LAST]生产数据{"
                           + Thread.currentThread().getName()
                                    + "}】y = " + y ;
                            deque.addLast(str);
                        }
                        // System.out.println(str);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "生产者-" + x).start();
        }
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("【FIRST-消费数据{"
                            + Thread.currentThread().getName()
                            + "}】" + deque.takeFirst());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者FIRST").start();
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("【LAST-消费数据{"
                            + Thread.currentThread().getName()
                            + "}】" + deque.takeLast());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者LAST").start();
    }
}
生产者和消费者模型的实现方案很多。对于双端阻塞队列一定要清楚它本身还是一个队列,如果现在first已经拽干净了,

那么将继续拽last,就会有可能出现first消费last的情况。

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class MLDNTestDemo {
    public static void main(String[] args) throws Exception {
        // 利用链表来实现
        BlockingDeque<String> deque = 
                            new LinkedBlockingDeque<String>();
        deque.addFirst("Hello-First");
        deque.addFirst("World-First");
        deque.addLast("Hello-Last");
        deque.addLast("World-Last");
        System.out.println(deque.takeFirst());
        System.out.println(deque.takeFirst());
        System.out.println(deque.takeFirst());
        System.out.println(deque.takeFirst());
    }
}
   如果有一端出现了阻塞,那么至少另外一端还可以供你使用。




阅读更多
个人分类: Java
上一篇JUC线程框架深度解析 — 04、并发集合支持类
下一篇JUC线程框架深度解析 — 06、DelayQueue延迟队列
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭