充分了解java阻塞队列机制

这篇具有很好参考价值的文章主要介绍了充分了解java阻塞队列机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

充分了解java阻塞队列机制,java,开发语言

1.阻塞队列

1.1 什么是 阻塞队列

public interface BlockingQueue<E> extends Queue<E> {
}

BlockingQueue继承了Queue的接口,是队列的一种,并且和Queue相比,BlockingQueue是线程安全的,多用于并发+并行编程,对于线程安全问题可以很好的解决.

下面是实现BlockingQueue接口的类

充分了解java阻塞队列机制,java,开发语言

怕大家理解不方便,俺通过思维导图的方式给大家呈现
充分了解java阻塞队列机制,java,开发语言

阻塞队列的典型例子就是BlockingQueue接口的实现类, 主要有六种实现
ArrayBlcokingQueue,LinkedBlockingQueue,SynchronousQueue,DelayQueue,PriorityBlockingQueue和 LinkedTransherQueue,它们各自有不同不同的特点。

1.2 阻塞队列的特点

在讲阻塞队列特点前,先给大家用图演示一下在没有阻塞队列时,服务器之间的联系.

充分了解java阻塞队列机制,java,开发语言
1.服务器A将接受到的请求传输给服务器B,他们之间联系是单线联系,也就是服务器A可以直接访问到服务器B,这样做会有一个很大的缺点,我们假设服务器A崩溃了,那么由于服务器B是和服务器A是相关联的,所以服务器B也会收到一定量的影响,甚至是一起崩溃…

充分了解java阻塞队列机制,java,开发语言

2.此时我们在来看这一张图,由于服务器A和B是密切关联的,所以当我想再让服务器A和C关联,我们不仅需要修改服务器A的代码,包括服务器B的代码我们也需要进行修改,此时如果再加上服务器D,E,F等等,经过这样的频繁修改代码,那便会对系统带来不可预估的损失.

3.所以我们在写代码时都会强调低耦合,给大家举例子来解释这个意思:

我们用苹果手机举例,由于苹果手机充电插口指定只有苹果官方的充电器才可以进行充电,所以我们可以看出,苹果手机如果想使用,只能依赖苹果官方充电器,如果没有这个充电器或者这个充电器坏了的话,那么苹果手机也就无法使用的.这就是高耦合,两者的依赖很深,谁都不能离开谁,其中一个坏掉,另一个也会收到影响.

我们再用安卓手机举例,由于安卓手机并没有指定必须是官方的充电器才可以充电,所以即使是这个充电器坏掉,俺也可以找到另一个充电器来平替,简单的叙述如下:若A与B存在依赖关系,那么当B发生改变时,A依然可以正常使用,此时就可以认为A与B时低耦合的.

那么我们如何解决这个耦合性高的问题呢?

俺们可以引入阻塞队列来降低它们之间的耦合性.

如下图:

充分了解java阻塞队列机制,java,开发语言

  1. 当我们引入阻塞队列后,就可以很优雅的解决耦合性高的问题.
    此时服务器A并不知道服务器B的存在,服务器A只认识阻塞队列,他的任务也就是将收到的请求添加到阻塞队列里面,服务器B同理,它也是只知道从阻塞队列里面读取请求,然后根据请求完成任务.此时不管是A,B那个服务器出现错误,另一个服务器也都不会收到影响.

充分了解java阻塞队列机制,java,开发语言

  1. 即使现在服务器C也从阻塞队列中读取请求,不过由于他们各个服务器之间并没有关联,所以服务器C的出现对其他服务器的影响也是微乎其微的.

  2. 3.阻塞队列还有一个功能就是削峰填谷,什么意思呢?

我们假设服务器A平时收到的请求是1000条/s,但是突然今天收到的请求是平常的好多倍

  • 当两个服务器没有使用阻塞队列时,服务器A的请求一股脑传给了服务器B,那么此时服务器B就会因为突然要处理的请求太多而导致程序崩溃.

    如图:不出意外,水杯里的水由于装不下就会溢出.
    充分了解java阻塞队列机制,java,开发语言

  • 当服务器之间添加了阻塞队列作为中介时,虽然A突然增添了许多请求给到阻塞队列中,但是并不影响B读取请求的速率,就像是下图,

充分了解java阻塞队列机制,java,开发语言
充分了解java阻塞队列机制,java,开发语言
这是削峰添谷的曲线图,其中灰色部分就是将多余的黄色部分填充得来的.

小结:阻塞功能使得生产者和消费者两端的能力得以平衡,当有任何一端速度过快时,阻塞队列便会把过快的速度降下来。

1.3 阻塞队列常用方法

充分了解java阻塞队列机制,java,开发语言
在阻塞队列中有很多的方法,而且非常相似,常用的8个方法主要以添加删除为主,主要分为三类:

1.抛出异常:addremoveelement
2.返回结果但是不抛出异常: offerpollpeek
3.阻塞:takeput

1.3.1 抛出异常:add、remove、element

add方法是往队列里面添加一个元素,如果队列满了,就会抛出异常来提示我们队列已满。

//源码
 public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

当插入元素失败时,就会抛出异常.测试代码如下:

public class Test1 {
    public static void main(String[] args) {
        //创建一个只有两个容量的阻塞队列
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
        System.out.println(blockingQueue.add(1));
        System.out.println(blockingQueue.add(1));
        System.out.println(blockingQueue.add(3));
    }
}

运行结果:

充分了解java阻塞队列机制,java,开发语言

remove方法是删除元素,如果我们队列为空的时候又进行了删除操作,同样会报NoSuchElementException,且在删除操作成功后会返回被删除的值。

public class Test1 {
    public static void main(String[] args) {
        //创建一个只有两个容量的阻塞队列
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
        blockingQueue.add(1);
        blockingQueue.add(2);
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        blockingQueue.remove();
    }
}

这里我们指定容量为2,并且添加两个元素,然后删除三个元素。结果如下

运行结果:

充分了解java阻塞队列机制,java,开发语言

element方法是返回队列的头节点,但是不会删除这个元素。当队列为空时同样会报NoSuchElementException的错误.

public class Test1 {
    public static void main(String[] args) {
        //创建一个只有两个容量的阻塞队列
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
        blockingQueue.element();
    }
}

此时我们对这个空队列返回队首元素.

运行结果:

充分了解java阻塞队列机制,java,开发语言

1.3.2 返回结果但是不抛出异常offer、poll、peek

offer方法用来插入一个元素,如果插入成功会返回true,如果队列满了,再插入元素不会抛出异常但是会返回false

public class Test1 {
    public static void main(String[] args) {
        //创建一个只有两个容量的阻塞队列
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
        System.out.println(blockingQueue.offer(1));
        System.out.println(blockingQueue.offer(1));
        System.out.println(blockingQueue.offer(1));
    }
}

此时队列的容量为2,当我们添加第三个元素之后就会返回false.

充分了解java阻塞队列机制,java,开发语言

poll方法和remove方法是对应的都是删除元素,都会返回删除的元素,但是当队列为空时则会返回null.

public class Test1 {
    public static void main(String[] args) {
        //创建一个只有两个容量的阻塞队列
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
        System.out.println(blockingQueue.poll());
    }
}

此时队列里没有元素,我们再进行poll就会返回null.

充分了解java阻塞队列机制,java,开发语言

peek方法和element方法对应,返回队列的头节点但并不删除,如果队列为空则直接返回null.

public class Test1 {
    public static void main(String[] args) {
        //创建一个只有两个容量的阻塞队列
        BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(2);
        System.out.println(blockingQueue.peek());
    }
}

此时队列里没有元素,如果进行peek会返回null.

充分了解java阻塞队列机制,java,开发语言

带超时时间的offer和poll

offer(E e, long timeout, TimeUnit unit){
}

它有三个参数,分别是元素、超时时长和时间单位。通常情况下,这个方法会插入成功并且返回true;如果队列满了导致插入不成功,在调用带超时时间重载方法的offer的时候,则会等待指定的超时时间,如果到了时间依然没有插入成功,则返回false。

E poll(long timeout, TimeUnit unit){
}

这个带参数的poll和上面的offer类似。如果能够移除,便会立即返回这个节点的内容;如果超过了我们定义的超时时间依然没有元素可以移除,便会返回null作为提示。

1.3.3 阻塞put和take

put:添加一个元素,如果队列此时满了就会进行阻塞.
take:删除队首元素,如果队列为空就会阻塞

put方法的作用是插入元素,通常在队列没有满的时候是正常插入。如果队列满了无法继续插入,这时它不会立刻返回false和抛出异常,而是让插入的线程进入阻塞状态,直到队列里面有空闲空间了。此时队列就会让之前的线程解除阻塞状态,并把刚才那个元素添加进去。 take方法的作用是获取并移除队列的头节点。通常队列里面有元素会正常取出数据并移除;但是如果执行take的时候队列里无数据,则阻塞,直到队列里面有数据以后,就会立即解除阻塞状态,并且取到数据.

1.3.4 小结

ArrayBlockingQueue是一个基于数组实现的有界的阻塞队列。

几个要点

  • ArrayBlockingQueue是一个用数组实现的队列,所以在效率上比链表结构的LinkedBlockingQueue要快一些,但是队列长度固定,不能扩展,入列和出列使用同一把锁。LinkedBlockingQueue是入列出列两把锁,读写分离。
  • 先进先出,FIFO,队列的头部 是在队列中存在时间最长的元素。队列的尾部 是在队列中存在时间最短的元素
  • 新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素
    利用重入锁来保证并发安全
  • 初始化时必须传入容量,也就是数组的大小,不需要扩容,因为是初始化时指定容量,并循环利用数组,使用之前一定要慎重考虑好容量
  • put(e)(put(e)时如果队列满了则使用notFull阻塞等待)、take()阻塞
  • add(e)时如果队列满了则抛出异常
  • remove()时如果队列为空则抛出异常
  • offer(e)时如果队列满了则返回false
  • poll()时如果队列为空则返回null
  • poll(timeout, unit)时如果队列为空则阻塞等待一段时间后如果还为空就返回null
    只使用了一个锁来控制入队出队,效率较低

1.4 常见的阻塞队列

1.4.1 ArrayListBlockingQueue

常见的构造方法如下

充分了解java阻塞队列机制,java,开发语言

下面是各个参数的意思.

public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c){
}

1.创建一个ArrayBlockingQueue,该队列具有给定(固定)容量、指定的访问策略,最初包含给定集合的元素,并按集合迭代器的遍历顺序添加。
2.参数:
capacity–此队列的容量
fair–如果为true,则在插入或删除时被阻止的线程的队列访问将按FIFO顺序进行处理(公平的,先来先处理);如果为false,则未指定访问顺序(也就是非公平的,其他线程就有可以插队的可能)。
c–最初包含的元素集合

对于ArrayListBlockingQueue类,它的内部是通过一个循环队列来实现的,这也就导致了它无法扩容,所以我们在创建这个队列时,一定要慎重考虑好容量.

那么我们该如何自己实现一个阻塞队列呢?

  1. 我们先实现一个普通的循环队列

“循环队列的优点:可以有效的利用资源。用数组实现队列时,如果不移动,随着数据的不断读写,会出现假满队列的情况

  1. 代码如下:
class MyBlockingQueue<E>{
    //自己实现阻塞队列
    //有take和put
    private Object object = new Object();
    private E[] array = (E[]) new Object[50];
    private int first = 0;//队首
    private int last = 0;//队尾

    //先进先出
    //循环队列
    //预留一个用来判断是满还是空的内存
    //first == last是空
    //(last+1)%array.length == first 是满
    
    //出队列
    public E take(){
         if(first == last){
             System.out.println("队列空了");
           	return null;
         }
         E value = array[first];
         first = (first+1)%array.length;//(49+1)%50=0
         return value;
     }
    }
    //进队列
 public void put(E value) {
              if((last+1)%array.length == first){
                System.out.println("队列满了");
				return;
            }
            array[last] = value;
            last = (last+1)%array.length;//(49+1)%50 == 0
        }

    }

}

上述代码实现的是一个普通的简化版循环队列,里面只有put和task方法.我们该怎么优化成带阻塞效果的队列呢?

那就是需要加锁,锁我们应该加在哪里呢?

根据需要我们了解,

  • 在take()时如果队列为空的话就进行阻塞,直到有新的元素添加进来,此时解除阻塞效果并将新添加的元素take()出去
  • 在put()操作时,如果队列满了的情况下就进行阻塞,直到有元素弹出队列,此时解除阻塞效果并将该元素添加到队尾.

我们通过上述两点需求我们可以这样写:

public E take() throws InterruptedException {
		//锁对象是this,谁调用这个方法谁就是this
        synchronized (this){
            //如果是空就wait
            if(first == last){
                System.out.println("队列空了");
                this.wait();
            }
            E value = array[first];
            first = (first+1)%array.length;//(49+1)%50=0
                //唤醒put方法
                //因为该代码块是加了锁的,所以即使是多线程情况下,当执行完take后,队列也一定不是满的.
                //此时就可以notify唤醒进行wait()的线程
                //如果不进行notify就可能会造成put方法一直阻塞下去
            this.notify();
            return value;

        }
    }
    //进队列
 public void put(E value) throws InterruptedException {
        synchronized (this){
            if((last+1)%array.length == first){
            //此时队列满了我们就需要进行阻塞
                System.out.println("队列满了");
                this.wait();
            }
            array[last] = value;
            last = (last+1)%array.length;//(49+1)%50 == 0
           
            //同上,由于我们的put方法加了锁,所以当进行put之后,该队列一定不是空的
            //此时便可以唤醒调用take方法的线程
                this.notify();
        }

    }

但是上述代码还有一点点bug,大家看下面的图:

充分了解java阻塞队列机制,java,开发语言

大家都知道,wait()是可以被唤醒的,假如我的代码写的并不严谨,其他的功能就有可能在我wait()的时候提前唤醒我,但是我此时队列还是空的呢,如果此时我take()那么一定会出现异常的.

那这个问题怎么解决?

我们可以改为while()来判断,如果是被其他代码唤醒,那么我还需要再判断队列是否为空,只有满足被唤醒并且队列不为空的情况下才可以继续运行下面的程序…

修改后的代码:

  public E take() throws InterruptedException {
        synchronized (this){
            //如果是空就wait
            while(first == last){//用while来判断
                System.out.println("队列空了");
                this.wait();
            }
            E value = array[first];
            first = (first+1)%array.length;//(49+1)%50=0
                //唤醒进队列
            this.notify();
            return value;

        }
    }
    //进队列
 public void put(E value) throws InterruptedException {
        synchronized (this){
            while((last+1)%array.length == first){//都用while
                System.out.println("队列满了");
                this.wait();
            }
            array[last] = value;
            last = (last+1)%array.length;//(49+1)%50 == 0
            //释放
                this.notify();
        }

    }

}

1.4.2 LinkedBlockingQueue

LinkedBlockingQueue内部使用链表实现的,如果我们不指定它的初始容量,那么它的默认容量就为整形的最大值Integer.MAX_VALUE,由于这个数特别特别的大,所以它也被称为无界队列。

1.4.3 SynchronousQueue

SynchronousQueue最大的不同之处在于,它的容量不同,所以没有地方来暂存元素,导致每次取数据都要先阻塞,直到有数据放入。同理,每次放数据的时候也会阻塞,直到有消费者来取。SynchronousQueue的容量不是1而是0,因为SynchronousQueue不需要去持有元素,它做的就是直接传递。

1.4.4 PriorityBlockingQueue

PriorityBlockingQueue是一个支持优先级的无界阻塞队列,可以通过自定义类实现compareTo()方法来制定元素排序规则,或者初始化时通过构造器参数Comparator来制定排序规则。同时,插入队列的对象必须是可比较大小的,也就是Comparable的,否则就会抛出ClasscastException异常。
它的take方法在队列为空时会阻塞,但是正因为它是无界队列,而且会自动扩容,所以它的队列永远不会满,所以它的put()方法永远不会阻塞,添加操作始终都会成功。

1.5 线程池对于阻塞队列的选择

充分了解java阻塞队列机制,java,开发语言文章来源地址https://www.toymoban.com/news/detail-612335.html

  • FixedThreadPool选取的是LinkedBlcokingQueue(同理SingleThreadExecutor) 首先我们知道LinkedBlockingQueu默认是无限长的,而FixedThreadPool的线程数是固定的,当核心线程数都在被使用时,这个时候如果进来新的任务会被放进阻塞队列中。由于队列是没有容量上限的,队列永远不会被填满,这样就保证了线程池FixedThreadPool和SingleThreadExecutor,不会拒绝新任务的提交,也不会丢失数据。
  • CachedThreadPool选取的是SynchronousQueue 首先CachedThreadPool的线程最大数量是无限的,也就意味着它的线程数不会受限制,那么它就不需要额外的空间来存储那些Task,因为每个任务都可以通过新建线程来处理。SynchronousQueue会直接把任务交给线程,不保存它们,效率更好。
  • ScheduledThreadPool选取的是延迟队列,对于ScneduledThreadPool而言,它使用的是DelayedWorkQueue,延迟队列的特点是:不是先进先出,而是会按照延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。选择使用延迟队列的原因是,ScheduledThreadPool处理的是基于时间而执行的Task,而延迟队列有能力把Task按照执行时间的

到了这里,关于充分了解java阻塞队列机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java高并发系列 - 第25天:掌握JUC中的阻塞队列

    这是java高并发系列第25篇文章。 环境:jdk1.8。 本文内容 掌握Queue、BlockingQueue接口中常用的方法 介绍6中阻塞队列,及相关场景示例 重点掌握4种常用的阻塞队列 Queue接口 队列是一种先进先出(FIFO)的数据结构,java中用Queue接口来表示队列。 Queue接口中定义了6个方法:

    2024年02月14日
    浏览(37)
  • 【Java】多线程案例(单例模式,阻塞队列,定时器,线程池)

    ❤️ Author: 老九 ☕️ 个人博客:老九的CSDN博客 🙏 个人名言:不可控之事 乐观面对 😍 系列专栏: 单例模式是设计模式之一。代码当中的某个类,只能有一个实例,不能有多个。单例模式分为:饿汉模式和懒汉模式 饿汉模式表示很着急,就想吃完饭剩下很多碗,然后一

    2024年02月06日
    浏览(43)
  • Java 多线程系列Ⅳ(单例模式+阻塞式队列+定时器+线程池)

    设计模式就是软件开发中的“棋谱”,软件开发中也有很多常见的 “问题场景”。针对这些问题场景,大佬们总结出了一些固定的套路。按照这些套路来实现代码可能不会很好,但至少不会很差。当前阶段我们需要掌握两种设计模式: (1)单例模式 (2)工厂模式 概念/特征

    2024年02月09日
    浏览(55)
  • 【Java系列】多线程案例学习——基于阻塞队列实现生产者消费者模型

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Java系列专栏】【JaveEE学习专栏】 本专栏旨在分享学习JavaEE的一点学习心得,欢迎大家在评论区交流讨论💌 什么是阻塞式队列(有两点): 第一点:当队列满的时候

    2024年02月04日
    浏览(52)
  • 探索Java并发编程利器:LockSupport,一种高效的线程阻塞与唤醒机制

    关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。 我们继续总结学习 Java基础知识 ,温故知新。 LockSupport 是 Java SE 9 及以上版本中引入的一个线程同步工具类,用

    2024年02月16日
    浏览(50)
  • 【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列

    目录 1、单例模式 1.1、饿汉模式 2.1、懒汉模式  2、阻塞队列 2.1、BlockingQueue 阻塞队列数据结构 对框架和设计模式的简单理解就是,这两者都是“大佬”设计出来的,让即使是一个代码写的不太好的“菜鸡程序员”也能写出还可以的代码。 设计模式也可以认为是对编程语言语

    2024年03月23日
    浏览(90)
  • Java - JUC(java.util.concurrent)包详解,其下的锁、安全集合类、线程池相关、线程创建相关和线程辅助类、阻塞队列

    JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题 java.lang.Thread.State tools(工具类):又叫信号量三组工具类,包含有 CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中

    2024年02月05日
    浏览(34)
  • 微服务---Redis实用篇-黑马头条项目-优惠卷秒杀功能(使用java阻塞队列对秒杀进行异步优化)

    1.1 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一人一单 5、扣减库存 6、创建订单 在这六

    2024年02月05日
    浏览(50)
  • 【Java】深入了解双亲委派机制(常说的类加载机制)

    ava虚拟机(JVM)的类加载机制是Java应用中不可或缺的一部分。本文将详细介绍JVM的双亲委派机制,并阐述各关键点。 双亲委派机制(Parent-Delegate Model)是Java类加载器中采用的一种类加载策略。该机制的核心思想是:如果一个类加载器收到了类加载请求,默认先将该请求委托

    2024年02月04日
    浏览(35)
  • 【数据结构-队列】阻塞队列

    💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学习,不断总结,共同进步,活到老学到老 导航 檀越剑指大厂系列:全面总

    2024年02月09日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包