Java多线程案例之阻塞队列详解

目录
  • 一.阻塞队列介绍
    • 1.1阻塞队列特性
    • 1.2阻塞队列的优点
  • 二.生产者消费者模型
    • 2.1阻塞队列对生产者的优化
  • 三.标准库中的阻塞队列
    • 3.1Java提供阻塞队列实现的标准类
    • 3.2Blockingqueue基本使用
  • 四.阻塞队列实现
    • 4.1阻塞队列的代码实现
    • 4.2阻塞队列搭配生产者与消费者的代码实现

一.阻塞队列介绍

1.1阻塞队列特性

阻塞队列特性:

一.安全性

二.产生阻塞效果

阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

  • 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.
  • 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 “生产者消费者模型”. 这是一种非常典型的开发模型.

1.2阻塞队列的优点

我们可以将阻塞队列比做成"生产者"和"消费者"的"交易平台".

我们可以把这个模型来比做成"包饺子"

A 的作用是擀饺子皮,也就是"生产者"

B 的作用是包饺子,也就是"消费者"

X 的作用一个当作放擀好饺子皮的一个盘中,也就是阻塞队列

这样我们根据A,B,X可以想象以下场景

场景一:

当A擀饺子皮的速度过快,X被A的杆好饺子皮放满了,这样A就需要停止擀饺子皮这一个操作,这时只能等待B来利用A提供的饺子皮包饺子后X所空出的空间,来给A提供生产的环境

场景二:

当B包饺子的速度过快,X被B的包饺子所用的饺子皮用空,这样B就需要停止包饺子这一个操作,这时只能等待A提供的饺子皮包饺子后X所存在饺子皮,来给B提供消费的环境

二.生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取

(1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

比如在 “秒杀” 场景下, 服务器同一时刻可能会收到大量的支付请求. 如果直接处理这些支付请求,服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程). 这个时候就可以把这些请求都放到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求.这样做可以有效进行 “削峰”, 防止服务器被突然到来的一波请求直接冲垮.

(2) 阻塞队列也能使生产者和消费者之间 解耦.

比如过年一家人一起包饺子. 一般都是有明确分工, 比如一个人负责擀饺子皮, 其他人负责包. 擀饺子皮的人就是 “生产者”, 包饺子的人就是 “消费者”.擀饺子皮的人不关心包饺子的人是谁(能包就行, 无论是手工包, 借助工具, 还是机器包), 包饺子的人也不关心擀饺子皮的人是谁(有饺子皮就行, 无论是用擀面杖擀的, 还是拿罐头瓶擀, 还是直接从超市买的).

2.1阻塞队列对生产者的优化

优化一:能够让多个服务器程序之间更充分的解耦合:

如果不使用生产者和消费者模型,此时A和B的耦合性比较强,如果A线程出现一些状况B就会挂,B线程出现一些状况A就会挂,这时当我们引入阻塞队列后我们就可以将A,B线程分开,如果A,B线程挂了有阻塞队列的存在下,是不会影响别的线程

优化二:能够对于请求进行"削峰填谷":

我们可以联想到我国的三峡大坝,三峡大坝就相当于阻塞队列,当我们遇到雨水大的季节,我们就可以关闭三峡大坝,利用三峡大坝来存水;当我们遇到干旱期,我们就可以打开三峡大坝的门,来放水解决干旱问题

三.标准库中的阻塞队列

3.1Java提供阻塞队列实现的标准类

java官方也提供了阻塞队列的标准类,主要有下面几个:

标准类 说明
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列
LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列
PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列
DelayQueue 一个使用优先级队列实现的无界阻塞队列
SynchronousQueue 一个不存储元素的阻塞队列
LinkedTransferQueue 一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列
BlockingQueue接口 单向阻塞队列实现了该接口
BlockingDeque接口 双向阻塞队列实现了该接口

3.2Blockingqueue基本使用

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.

BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.

put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.

BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();

四.阻塞队列实现

4.1阻塞队列的代码实现

我们通过 “循环队列” 的方式来实现

使用 synchronized 进行加锁控制.put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定队列就不满了, 因为同时可能是唤醒了多个线程).take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)

我们在设计阻塞队列的时候可以将队列联想成一个圆

class BlockingQueue{
    //队列里存放的个数
   volatile private int size = 0;
    //队列的头节点
    private int head = 0;
    //队列的尾节点
    private int prov = 0;
    //创建一个数组,我们来给这个数组的容量设置为100
    private int[] array = new int[100];
    //创建一个专业的锁对象
    private Object object = new Object();
    //实现阻塞队列中的put方法
    public void put(int value) throws InterruptedException {
        synchronized (object) {
            //当数组已经满了
            if (size == array.length) {
                object.wait();
            } else {
                //我们可以优化成prov = (prov + 1) % items.length
                array[prov] = value;
                prov ++;
               if (prov >= array.length) {
                   prov = 0;
               }
            }
            size++;
            object.notify();
        }
    }
    //实现阻塞队列中的take方法
    public int take() throws InterruptedException {
        synchronized (object) {
            if (size == 0) {
                object.wait();
            }
            int x = array[head];
            head++;
            if (head >= array.length) {
                head = 0;
            }
            size--;
            object.notify();
            return x;
        }
    }
}

4.2阻塞队列搭配生产者与消费者的代码实现

class BlockingQueue{
    //队列里存放的个数
   volatile private int size = 0;
    //队列的头节点
    private int head = 0;
    //队列的尾节点
    private int prov = 0;
    //创建一个数组,我们来给这个数组的容量设置为100
    private int[] array = new int[100];
    //创建一个专业的锁对象
    private Object object = new Object();
    //实现阻塞队列中的put方法
    public void put(int value) throws InterruptedException {
        synchronized (object) {
            //当数组已经满了
            if (size == array.length) {
                object.wait();
            } else {
                //我们可以优化成prov = (prov + 1) % items.length
                array[prov] = value;
                prov ++;
               if (prov >= array.length) {
                   prov = 0;
               }
            }
            size++;
            object.notify();
        }
    }
    //实现阻塞队列中的take方法
    public int take() throws InterruptedException {
        synchronized (object) {
            if (size == 0) {
                object.wait();
            }
            int x = array[head];
            head++;
            if (head >= array.length) {
                head = 0;
            }
            size--;
            object.notify();
            return x;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = new BlockingQueue();
        Thread thread1 = new Thread(()-> {
            while (true) {
                for (int i = 0; i < 100; i++) {
                    try {
                        blockingQueue.put(i);
                        System.out.println("生产了"+i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread2 = new Thread(()->{
                while (true) {
                    try {
                        int b = blockingQueue.take();
                        System.out.println("消耗了"+b);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        });
        thread1.start();
        thread2.start();
    }
}

以上就是Java多线程案例之阻塞队列详解的详细内容,更多关于Java多线程阻塞队列的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java多线程之多种锁和阻塞队列

    一.悲观锁和乐观锁 1.1. 乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制. 乐观锁适用于多读的应用类型,乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的. CAS全称 Compare And Swap(比较与交换),是一种无锁算法.在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步.java.util

  • Java阻塞队列的实现及应用

    目录 1.手写生产者消费者模型 2.手写定时器 总结 1.手写生产者消费者模型 所谓生产者消费者模型,可以用我们生活中的例子来类比:我去一个小摊儿买吃的,老板把已经做好的小吃都放在摆盘上,供我挑选.那么,老板就是生产者:我就是消费者:摆盘就是阻塞队列,用来当做生产与消费的缓冲区.因此,阻塞队列在生产者与消费者模型中起着至关重要的缓冲作用. 此次先演示如何手写阻塞队列(也可以使用Java库中自带的阻塞队列). 手写的阻塞队列只实现最基础的两个功能:入队和出队.之所以叫阻塞队列,是因为当队空或者队满

  • 利用Java手写阻塞队列的示例代码

    目录 前言 需求分析 阻塞队列实现原理 线程阻塞和唤醒 数组循环使用 代码实现 成员变量定义 构造函数 put函数 offer函数 add函数 take函数 重写toString函数 完整代码 总结 前言 在我们平时编程的时候一个很重要的工具就是容器,在本篇文章当中主要给大家介绍阻塞队列的原理,并且在了解原理之后自己动手实现一个低配版的阻塞队列. 需求分析 在前面的两篇文章ArrayDeque(JDK双端队列)源码深度剖析和深入剖析(JDK)ArrayQueue源码当中我们仔细介绍了队列的原理,

  • Java多线程(单例模式,阻塞队列,定时器,线程池)详解

    目录 1. 单例模式(singleton pattern) 1.1 懒汉模式 1.2 饿汉模式 2 阻塞队列(blocking queue) 2.1 阻塞队列 2.2 生产者消费者模型 2.3 标准库中的阻塞队列 2.4 实现阻塞队列 3. 定时器 3.1 标准库中的定时器 3.2 实现定时器 4 线程池 4.1 标准库中的线程池 4.2 Executors 创建线程池的几种方式 4.3 利用线程池 创建多线程计算fib 数 4.4 实现线程池 1. 单例模式(singleton pattern

  • Java多线程常见案例分析线程池与单例模式及阻塞队列

    目录 一.单例模式 1.饿汉模式 2.懒汉模式(单线程) 3.懒汉模式(多线程) 二.阻塞队列 阻塞队列的实现 生产者消费者模型 三.线程池 1.创建线程池的的方法 (1)ThreadPoolExecutor (2)Executors(快捷创建线程池的API) 2.线程池的工作流程 一.单例模式 设计模式:软件设计模式 是一套被反复使用.多数人知晓.经过分类编目.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性.程序的重用性. 单例模式:是设计模式的一种.

  • Java中常用阻塞队列的问题小结

    Java常用阻塞队列 ArrayBlockingQueue 内部由一个固定长度的数组来实现阻塞队列 /** The queued items */ final Object[] items; /** items index for next take, poll, peek or remove */ int takeIndex; /** items index for next put, offer, or add */ int putIndex; public ArrayBlockingQue

  • Java多线程案例之阻塞队列详解

    目录 一.阻塞队列介绍 1.1阻塞队列特性 1.2阻塞队列的优点 二.生产者消费者模型 2.1阻塞队列对生产者的优化 三.标准库中的阻塞队列 3.1Java提供阻塞队列实现的标准类 3.2Blockingqueue基本使用 四.阻塞队列实现 4.1阻塞队列的代码实现 4.2阻塞队列搭配生产者与消费者的代码实现 一.阻塞队列介绍 1.1阻塞队列特性 阻塞队列特性: 一.安全性 二.产生阻塞效果 阻塞队列是一种特殊的队列. 也遵守 “先进先出” 的原则.阻塞队列能是一种线程安全的数据结构, 并且具有

  • Java并发编程之阻塞队列详解

    1.什么是阻塞队列? 队列是一种数据结构,它有两个基本操作:在队列尾部加入一个元素,从队列头部移除一个元素.阻塞队里与普通的队列的区别在于,普通队列不会对当前线程产生阻塞,在面对类似消费者-生产者模型时,就必须额外的实现同步策略以及线程间唤醒策略.使用阻塞队列,就会对当前线程产生阻塞,当队列是空时,从队列中获取元素的操作将会被阻塞,当队列是满时,往队列里添加元素的操作也会被阻塞. 2.主要的阻塞队列及其方法 java.util.concurrent包下提供主要的几种阻塞队列,主要有以下几个: 1

  • Java 阻塞队列详解及简单使用

     Java 阻塞队列详解 概要: 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全"传输"数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利.本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景. 认识BlockingQueue阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示: 从上图我们可以很清楚看到,通过一个共享的队列,

  • Java多线程之搞定最后一公里详解

    目录 绪论 一:线程安全问题 1.1 提出问题 1.2 不安全的原因 1.2.1 原子性 1.2.2 代码"优化" 二:如何解决线程不安全的问题 2.1 通过synchronized关键字 2.2 volatile 三:wait和notify关键字 3.1 wait方法 3.2 notify方法 3.3 wait和sleep对比(面试常考) 四:多线程案例 4.1 饿汉模式单线程 4.2 懒汉模式单线程 4.3 懒汉模式多线程低性能版 4.4懒汉模式-多线程版-二次判断-性能高 总结

  • Java多线程中ReentrantLock与Condition详解

    一.ReentrantLock类 1.1什么是reentrantlock java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为语言的特性来实现.这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法.性能特性或者锁定语义.ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性.此外,它还提供了在激烈争用情况下更

  • Java多线程之哲学家就餐问题详解

    一.题目 教材提供一个哲学家就餐问题的解决方案的框架.本问题要求通过pthreads 互斥锁来实现这个解决方案. 哲学家 首先创建 5 个哲学家,每个用数字 0~4 来标识.每个哲学家作为一个单独的 线程运行. 可使用 Pthreads 创建线程.哲学家在思考和吃饭之间交替.为了模拟这两种活动,请让线程休眠 1 到 3 秒钟.当哲学家想要吃饭时,他调用函数: pickup_forks(int philosopher _number) 其中,philosopher _number 为想吃饭哲学家的

  • Java多线程读写锁ReentrantReadWriteLock类详解

    目录 ReentrantReadWriteLock 读读共享 写写互斥 读写互斥 源码分析 写锁的获取与释放 读锁的获取与释放 参考文献 真实的多线程业务开发中,最常用到的逻辑就是数据的读写,ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的.所以在JDK中提供了一种读写锁ReentrantReadWriteLock类,使用它可以加快运行效率. 读写锁表示两个锁,一个是读操作相关的锁

  • Java多线程同步工具类CountDownLatch详解

    目录 简介 核心方法 CountDownLatch如何使用 CountDownLatch运行流程 运用场景 总结 简介 CountDownLatch是一个多线程同步工具类,在多线程环境中它允许多个线程处于等待状态,直到前面的线程执行结束.从类名上看CountDown既是数量递减的意思,我们可以把它理解为计数器. 核心方法 countDown():计数器递减方法. await():使调用此方法的线程进入等待状态,直到计数器计数为0时主线程才会被唤醒. await(long, TimeUnit):在

  • java多线程关键字final和static详解

    这篇文章主要介绍了java多线程关键字final和static详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 final关键字 1.final关键字在单线程中的特点: 1)final修饰的静态成员:必须在进行显示初始化或静态代码块赋值,并且仅能赋值一次. 2)final修饰的类成员变量,可以在三个地方进行赋值:显示初始化.构造代码块和构造方法,并且仅能赋值一次. 3)final修饰的局部变量,必须在使用之前进行显示初始化(并不一定要在定义是

  • java 多线程与并发之volatile详解分析

    目录 CPU.内存.缓存的关系 CPU缓存 什么是CPU缓存 为什么要有多级CPU Cache Java内存模型(Java Memory Model,JMM) JMM导致的并发安全问题 可见性 原子性 有序性 volatile volatile特性 volatile 的实现原理 总结 CPU.内存.缓存的关系 要理解JMM,要先从计算机底层开始,下面是一份大佬的研究报告 计算机在做一些我们平时的基本操作时,需要的响应时间是不一样的!如果我们计算一次a+b所需要的的时间: CPU读取内存获得a,1

随机推荐