【JUC】阻塞队列

【JUC】阻塞队列

        • 1.什么是阻塞队列?
        • 2.有什么用?
        • 3.JUC中的BlockingQueue
        • 4.用在哪里?

1.什么是阻塞队列?

  • 顾名思义,首先它是个队列:
    【JUC】阻塞队列_第1张图片
  • 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
  • 当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。
  • 同样,试图往已满的阻塞队列中添加新的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素,或者全清空队列后,使队列重新变得空闲起来,并后续新增。

2.有什么用?

  • 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即线程阻塞),一旦条件满足,被挂起的线程优惠被自动唤醒
  • Java中在JUC包里定义了阻塞队列的接口BlockingQueue
  • 在JUCt包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,这会给程序带来不小的复杂度
  • 而现在,我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,BlockingQueue都一手包办了

3.JUC中的BlockingQueue

  • 在JUC包里找到它,用idea右键打开它的类图:

【JUC】阻塞队列_第2张图片

  • BlockingQueue继承于Queue接口,为了便于理解,我顺便把List接口的类图也打开了部分,List接口和Queue接口都继承于Collection接口。
  • 我们先看List接口中的部分实现类,像ArrayList,LinkedList大家应该不陌生,CopyOnWriteArrayList之前在聊ArrayList线程不安全验证和解决时聊过,详见:
  • Java集合类ArrayList线程不安全验证和解决
  • 有List接口和其实现做类比,我们再看BlockingQueue接口就相对容易些。

3.1 先看BlockingQueue的核心方法:
【JUC】阻塞队列_第3张图片

  • add():添加元素,remove():移除元素,两个方法在失败时会抛出异常。
  • offer():添加元素,poll():移除元素,两个方法在成功时返回true,失败时返回false。
  • put():添加元素,take():一次元素,两个方法会一直阻塞直到成功或是中断退出

3.2 BlockingQueue的几个实现类:

  • ArrayBlockingQueue:由数组组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表组成的有界阻塞队列(注:比较坑的是其默认大小是Interger.MAX_VALUE)
  • SynchronousQueue:不存储元素,既单个元素的阻塞队列
  • PriorityBlockingQueue:支持优先级的无界阻塞队列
  • DelayQueue:支持优先级的延迟无界阻塞队列
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列
  • LinkedBlockingDeque:有链表结构组成的双向阻塞队列。

4.用在哪里?

  • 生产者消费者模式
  • 线程池
  • 消息中间件
    4.1 生产者消费者模式
  • 传统版生产者消费者使用Lock实现
  • -Demo1:
/**
 * 生产者消费者传统版
 *
 * @author wangjie
 * @version V1.0
 * @date 2019/12/24
 */
public class ProdConsumerTraditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.deIncrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();
    }
}

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

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

    public void deIncrement() throws Exception {
        lock.lock();
        try {
            //判断
            while (num == 0) {
                //等待 不生产
                condition.await();
            }
            //干活
            num--;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            //通知唤醒
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
  • 利用num的值进行线程间的交换,num=0即生产者生产,num=1即消费者消费
  • 运行结果:
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0

Process finished with exit code 0

  • 阻塞队列版生产者消费者:
  • Demo2:
/**
 * 阻塞队列生产者消费者
 *
 * @author wangjie
 * @version V1.0
 * @date 2019/12/24
 */
public 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();
    }
}

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

    private BlockingQueue blockingQueue = null;

    public MyResource(BlockingQueue 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;
    }
}
  • 运行结果:
java.util.concurrent.ArrayBlockingQueue
Prod	生产线程启动
consumer	消费线程启动
Prod	 插入队列数据1成功
consumer消费队列1成功
Prod	 插入队列数据2成功
consumer消费队列2成功
Prod	 插入队列数据3成功
consumer消费队列3成功
Prod	 插入队列数据4成功
consumer消费队列4成功
Prod	 插入队列数据5成功
consumer消费队列5成功



时间到,停止活动
Prod	 插入队列数据6成功
consumer消费队列6成功
Prod	 停止 表示 flagfalse

Process finished with exit code 0

  • 如果扒一下ArrayBlockingQueue的源码就会发现:
public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }
     public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
  • 其底层也有用ReentrantLock 实现的,至于ReentrantLock ,我们之前扒过其源码,有兴趣的可以转到:【锁】【JUC】可重入锁/AQS队列–ReentrantLock源码分析

4. 线程池

  • 在线程池中阻塞队列用来缓存等待执行的任务,详细可见:
  • 【JUC】线程池-ThreadPoolExecutor源码解析

4.2 消息中间件后续再聊。。。
【完】
注:文章内所有测试用例源码:https://gitee.com/wjie2018/test-case.git

你可能感兴趣的:(java,JUC,并发,队列,java,多线程,并发编程)