阻塞队列BlockingQueue

344 阅读7分钟

引言:更多相关请看 JAVA并发编程系列

概述

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如图所示:

线程1往阻塞队列中添加元素二线程2从队列中移除元素:
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。
同样,试图往已满的阻塞队列中添加新长度度的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增。

作用与好处

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程优惠被自动唤醒。 需要使用BlockingQueue的好处是程序员无需关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为BlockingQueue都一手给你包办好了。在java.util.concurrent包发布以前的多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,给我们的程序带来不小的复杂度。

BlockingQueue的核心方法

执行类型 详情
抛出异常 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full。当阻塞队列空时,再往队列Remove/element元素时会抛出NoSuchElementException。
特殊值 插入方法,成功返回true 失败返回false。移除或检查方法,成功返回元素,队列里面没有就返回null。
一直阻塞 当阻塞队列满时,生产者继续往队列里面put元素,队列会一直阻塞直到put数据or响应中断退出。当阻塞队列空时,消费者试图从队列take元素,队列会一直阻塞消费者线程直到队列可用。
超时退出 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出。offer超时插入失败退出会返回false,poll超时取出失败退出会返回null

案例:

/**
 * BlockingQueue的API
 */
class BlockingQueueDemo {
    public static void main(String[] args) {
//        thrExp();
//        specialVal();
//        block();
        waitting();
    }

    /**
     * 等待
     */
    public static void waitting() {
        // 设定容量为3的数组有界阻塞队列.
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        try {
            System.out.println(queue.offer("a", 200, TimeUnit.MICROSECONDS));
            System.out.println(queue.offer("b", 200, TimeUnit.MICROSECONDS));
            System.out.println(queue.offer("c", 200, TimeUnit.MICROSECONDS));
            System.out.println(queue.poll(200,TimeUnit.MICROSECONDS));
            // 等待200毫秒,没有空位的话就返回false
            System.out.println(queue.offer("d", 500, TimeUnit.MICROSECONDS));
            System.out.println(queue.offer("e", 500, TimeUnit.MICROSECONDS));

            System.out.println(queue.poll(200,TimeUnit.MICROSECONDS));
            System.out.println(queue.poll(200,TimeUnit.MICROSECONDS));
            System.out.println(queue.poll(200,TimeUnit.MICROSECONDS));
            // 等待200毫秒,没有元素的话就返回null
            System.out.println(queue.poll(200,TimeUnit.MICROSECONDS));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 堵塞
     */
    public static void block() {
        // 设定容量为3的数组有界阻塞队列.
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        try {
            queue.put("a");
            System.out.println(Thread.currentThread().getName() + "\t放入");
            queue.put("b");
            System.out.println(Thread.currentThread().getName() + "\t放入");
            queue.put("c");
            System.out.println(Thread.currentThread().getName() + "\t放入");
            // 阻塞着
//            queue.put("d");
//            System.out.println(Thread.currentThread().getName()+"\t放入");

            System.out.println(Thread.currentThread().getName() + "\t获取" + queue.take());
            System.out.println(Thread.currentThread().getName() + "\t获取" + queue.take());
            System.out.println(Thread.currentThread().getName() + "\t获取" + queue.take());
            // 阻塞着
//            System.out.println(Thread.currentThread().getName()+"\t获取"+queue.take());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 特殊值
     */
    public static void specialVal() {
        // 设定容量为3的数组有界阻塞队列.
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        // false
//        System.out.println(queue.offer("d"));

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        // null
//        System.out.println(queue.poll());
        // null
//        System.out.println(queue.peek());
    }

    /**
     * 抛出异常
     */
    public static void thrExp() {
        // 设定容量为3的数组有界阻塞队列.
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        // java.lang.IllegalStateException: Queue full
//        System.out.println(queue.add("d"));

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        // java.util.NoSuchElementException
//        System.out.println(queue.remove());
        // java.util.NoSuchElementException
        System.out.println(queue.element());
    }

}

架构梳理+种类分析

架构介绍

种类分析

最重要的是前面这三种标红的。
ArrayBlockingQueue: 由数组结构组成的有界阻塞队列。
LinkedBlockingQeque: 由链表结构组成的有界(但大小默认值Integer>MAX_VALUE)阻塞队列。
SynchronousQueue:不存储元素的阻塞队列,也即是单个元素的队列。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue: 使用优先级队列实现的延迟无界阻塞队列。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

SynchronousQueue

SynchronousQueue没有容量,与其他BlcokingQueue不同,SynchronousQueue是一个不存储元素的BlcokingQueue。每个put操作必须要等待现有一个take操作,否则不能继续添加元素,反之亦然。
案例:

/**
 * 阻塞队列SynchronousQueue演示
 */
class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "\t put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "\t put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AAA").start();

        new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(200);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
                TimeUnit.MILLISECONDS.sleep(200);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
                TimeUnit.MILLISECONDS.sleep(200);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BBB").start();
    }
}

效果:

应用场景

生产者消费者模式、线程池、消息中间件。

生产者消费者模式

传统版

描述:一个初始值为0的变量,两个线程交替操作,一个加1,一个减1来5轮。

/**
 * 共享资源类
 */
class LockData {
    private int num = 0;
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws Exception {
        lock.lock();
        try {
            //判断
            while (num == 1) {
                //等待 不生产
                condition.await();
            }
            //干活
            num++;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            //通知唤醒
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() throws Exception {
        lock.lock();
        try {
            //判断
            while (num == 0) {
                //等待 不生产
                condition.await();
            }
            //干活
            num--;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            //通知唤醒
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 三步骤
 * 判断
 * 干活
 * 唤醒
 */
class SynData {
    private int num;

    public synchronized void increment() throws Exception {
        while (num == 1) {
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        this.notifyAll();
    }

    public synchronized void decrement() throws Exception {
        while (num == 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t" + num);
        this.notifyAll();
    }
}

/**
 * Description
 * 一个初始值为0的变量 两个线程交替操作 一个加1 一个减1来5轮
 **/
class ProdConsumerTDemo {
    public static void main(String[] args) throws Exception {
        // ReentrantLock版
        LockData lockData = new LockData();
        // synchronized版
        SynData synData = new SynData();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    lockData.increment();
//                    synData.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    lockData.decrement();
//                    synData.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();
    }
}

消费队列版

class MyResource {
    /**
     * 默认开启 进行生产消费的交互
     */
    private volatile boolean flag = true;
    /**
     * 默认值是0
     */
    private AtomicInteger atomicInteger = new AtomicInteger();

    private BlockingQueue<String> blockingQueue = null;

    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProd() throws Exception {
        String data = null;
        boolean returnValue;
        while (flag) {
            data = atomicInteger.incrementAndGet() + "";
            returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
            if (returnValue) {
                System.out.println(Thread.currentThread().getName() + "\t 插入队列数据" + data + "成功");
            } else {
                System.out.println(Thread.currentThread().getName() + "\t 插入队列数据" + data + "失败");
            }
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
    }

    public void myConsumer() throws Exception {
        String result = null;
        while (flag) {
            result = blockingQueue.poll(2L, TimeUnit.SECONDS);
            if(null==result||"".equalsIgnoreCase(result)){
                flag=false;
                System.out.println(Thread.currentThread().getName()+"\t"+"超过2m没有取到 消费退出");
                System.out.println();
                System.out.println();
                return;
            }
            System.out.println(Thread.currentThread().getName() + "消费队列" + result + "成功");

        }
    }
    public void stop() throws Exception{
        flag=false;
    }
}

/**
 * Description
 * volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
 **/
class ProdConsumerBlockQueueDemo {
    public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t生产线程启动");
            try {
                myResource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"Prod").start();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t消费线程启动");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"consumer").start();
        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println("时间到,停止活动");
        myResource.stop();
    }
}

Synchronized和lock区别

1.原始构成
synchronized是关键字属于JVM层面,monitorenter和monitorexit。底层通过monitor对象来完成,其实wait/notify方法也依赖与monitor对象,只有在同步方法或同步代码块才能调用wait和notify方法。
lock是具体类java.util.concurrent.locks.Lock,属于api层次的锁。
2.使用方法
synchronized属于自动释放锁,ReentrantLock需要手动释放,不手动释放会出现死锁现象。lock()和unlock()需要通过try/finaly语句块来完成。
3.等待释放可以打断
synchronized不可以打断,除非抛出异常或正常执行完毕。
ReentrantLock可以打断,1设置超时方法tryLock(long timeout, TimeUnit unit);2.lockInterruptibly()放代码块中,调用interrput中断。
4.加锁是否公平
synchronized是非公平锁。ReentrantLock二者皆可,可通过构造方法布尔值指定,true公平锁,false非公平锁。 5.锁是否可以绑定多个条件Condition
synchronized没有,ReentrantLock有,可精准控制,不像synchronized那样要么随机唤醒一个线程,要么全部线程唤醒。

案例

描述:多线程顺序调用:A-B-C,轮流打印A 5次,B 10次,C 15次。来10轮。

/**
 * 共享资源类
 */
class LockData {
    private int num = 1;// A.1 B.2 C.3
    private ReentrantLock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void p5() throws Exception {
        lock.lock();
        try {
            //判断
            while (num != 1) {
                //等待 不生产
                c1.await();
            }
            //干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t打印第" + i + "次\t" + num);
            }
            //通知唤醒
            num = 2;
            c2.signal();
        } finally {
            lock.unlock();
        }
    }

    public void p10() throws Exception {
        lock.lock();
        try {
            //判断
            while (num != 2) {
                //等待 不生产
                c2.await();
            }
            //干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t打印第" + i + "次\t" + num);
            }
            //通知唤醒
            num = 3;
            c3.signal();
        } finally {
            lock.unlock();
        }
    }

    public void p15() throws Exception {
        lock.lock();
        try {
            //判断
            while (num != 3) {
                //等待 不生产
                c3.await();
            }
            //干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t打印第" + i + "次\t" + num);
            }
            //通知唤醒
            num = 1;
            c1.signal();
        } finally {
            lock.unlock();
        }
    }


}

/**
 * Description
 * 一个初始值为0的变量 两个线程交替操作 一个加1 一个减1来5轮
 **/
class ProdConsumerTDemo {
    public static void main(String[] args) {
        LockData lockData = new LockData();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    lockData.p5();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    lockData.p10();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    lockData.p15();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

    }
}