阻塞队列相关

1.什么是阻塞队列?
  • 顾名思义,首先它是个队列:

    image-20210207103856304

  • 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。

  • 当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。

  • 同样,试图往已满的阻塞队列中添加新的线程同样也会被阻塞,直到其他线程从队列中移除一个或者多个元素或者全清空队列后使队列重新变得空闲起来并后续新增。

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

image-20210207103921160

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

3.1 先看BlockingQueue的核心方法:
image-20210207103935836

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

3.2 BlockingQueue的几个实现类:

  • ArrayBlockingQueue:由数组组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表组成的有界阻塞队列(注:比较坑的是其默认大小是Interger.MAX_VALUE)
  • SynchronousQueue:不存储元素,既但个元素的阻塞队列
  • PriorityBlockingQueue:支持优先级的无界阻塞队列
  • DelayQueue:支持优先级的延迟无界阻塞队列
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列
  • LinkedBlockingQeque:有链表结构组成的双向阻塞队列。
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<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;
    }
}
  • 运行结果:
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();
        }
    }

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

终极之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值