Java阻塞队列的实现及应用

目录
  • 1.手写生产者消费者模型
  • 2.手写定时器
  • 总结

1.手写生产者消费者模型

所谓生产者消费者模型,可以用我们生活中的例子来类比:我去一个小摊儿买吃的,老板把已经做好的小吃都放在摆盘上,供我挑选。那么,老板就是生产者;我就是消费者;摆盘就是阻塞队列,用来当做生产与消费的缓冲区。因此,阻塞队列在生产者与消费者模型中起着至关重要的缓冲作用。

此次先演示如何手写阻塞队列(也可以使用Java库中自带的阻塞队列)。

手写的阻塞队列只实现最基础的两个功能:入队和出队。之所以叫阻塞队列,是因为当队空或者队满的时候,都要实现阻塞,直到队中不空或不满的时候,才会取消阻塞。

手写阻塞队列实现如下:

//阻塞队列BlockQueue
static class BlockQueue{
    //该队列用一个数组来实现,我们让此队列的最大容量为10
        private int[] items = new int[10];
        private int head = 0;
        private int tail = 0;
        private int size = 0;
        private Object locker =new Object();
        //入队
        public void put(int item) throws InterruptedException {
            synchronized(locker) {
                while (size == items.length) {
                    //入队时,若队满,阻塞
                    locker.wait();
                }
                items[tail++] = item;
                //如果到达末尾,重回队首(实现循环队列)
                if (tail >= items.length) {
                    tail = 0;
                }
                size++;
                locker.notify();
            }
        }
        //出队
        public int back() throws InterruptedException {
            int ret = 0;
            synchronized (locker) {
                while (size == 0) {
                    //出队时,若队空,阻塞
                    locker.wait();
                }
                ret = items[head++];
                if (head >= items.length) {
                    head = 0;
                }
                size--;
                locker.notify();
            }
            return ret;
        }
    }

用两个线程充当生产者与消费者:

public static void main(String[] args) throws InterruptedException {
        BlockQueue blockQueue = new BlockQueue();
        //生产者线程
        Thread produce = new Thread(){
            @Override
            public void run() {
                for(int i = 0;i<10000;++i){
                    try {
                        System.out.println("生产了:"+i);
                        blockQueue.put(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        produce.start();
        //消费者线程
        Thread customer = new Thread(){
            @Override
            public void run() {
                while (true) {
                    try {
                        int res = blockQueue.back();
                        System.out.println("消费了:" + res);
                        //每次消费后等1秒,也就是生产的快,消费的慢
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        customer.start();
        customer.join();
        produce.join();
    }

结果如下:可以看到,生产者线程先生产元素,(阻塞队列容量为10),当队列满时,队列阻塞,消费者线程消费元素,因为消费的慢,所以接下来生产者线程由于阻塞队列不能快速生产,只能等待消费者线程消费队列中的元素,生产者线程才能随着生产,这就是阻塞队列的缓冲作用。

2.手写定时器

先看一下Java包中的定时器。

下面的代码我们通过调用timer类中的schedule方法来实现定时器功能。schedule方法有两个参数,第一个参数:要执行的任务,第二个参数:时间。

下面的代码中,schedule方法中的第一个任务参数:我们创建了一个TimerTask实例;重写里面的run方法来打印"触发定时器"这句话。第二个参数:3000;表示3秒后执行这个任务。

import java.util.Timer;
import java.util.TimerTask;
public class Test{
public static void main(String[] args) {
        Timer timer = new Timer();
        System.out.println("代码开始执行");
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("触发定时器");
            }
        },3000);
    }
}

结果如下:

从上面就可以看出来我们手写定时器需要实现以下两个方面:

1.一个Task类,用来描述要实现的任务

2.一个Timer类,类中再实现一个schedule方法

Task类实现

//Task类用来描述任务,它继承Comparable接口是因为要将任务放到优先级阻塞队列中
    static class Task implements Comparable<Task>{
        //command表示这个任务是什么
        private Runnable command;
        //time是一个时间戳
        private long time;
        public Task(Runnable command,long time){
            this.command = command;
            this.time = System.currentTimeMillis()+time;
        }
        public void run(){
            command.run();
        }
        //因为要将Task任务放到优先级阻塞队列中,所以要重写compareTo方法,我们将时间短的任务放到队头
        @Override
        public int compareTo(Task o) {
            return (int)(this.time - o.time);
        }
    }

Timer类实现

//Timer类中需要有一个定时器,还需要有一个schedule方法
    static class Timer{
        //使用优先级阻塞队列来放这些任务,这样才能把最接近时钟的任务放到队头,我们每次扫描队头任务就行了
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        //locker用来解决忙等问题
        private Object locker = new Object();
        //构造方法中完成定时器功能
        public Timer(){
            //需要构造一个线程,来不断地扫描队头,来判断队头任务是否到点,也就是是否该开始执行了
            Thread t = new Thread(){
                @Override
                public void run() {
                    while(true){
                        //取出队首任务来判断是否到时间了
                        try {
                            Task task = queue.take();
                            long current = System.currentTimeMillis();
                            //当前时间戳小于时钟时间戳,表明时间还没到,那就等待
                            if (current < task.time){
                                queue.put(task);
                                synchronized (locker){
                                    locker.wait(task.time-current);
                                }
                            }else{
                                //否则时间到,开始执行任务
                                task.run();
                            }
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                                break;
                            }
                        }
                    }
                };
            t.start();
            }
        //schedule方法的两个参数,command为任务,delay为一个时间差例如:3000(单位为毫秒)
        public void schedule(Runnable command,long delay){
            Task task = new Task(command,delay);
            queue.put(task);
            synchronized (locker){
                locker.notify();
            }
        }
    }

主线程

public static void main(String[] args) {
        System.out.println("程序启动");
        Timer timer = new Timer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("触发定时器");
            }
        },3000);//3000表示定时时间为3秒
    }

结果如下:“程序启动” 在程序启动是立刻显示出来;“触发定时器”在3秒后显示出来。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 详解Java七大阻塞队列之SynchronousQueue

    目录 分析 其实SynchronousQueue 是一个特别有意思的阻塞队列,就我个人理解来说,它很重要的特点就是没有容量. 直接看一个例子: package dongguabai.test.juc.test; import java.util.concurrent.SynchronousQueue; /** * @author Dongguabai * @description * @date 2021-09-01 21:52 */ public class TestSynchronousQu

  • Java 阻塞队列和线程池原理分析

    目录 [1]阻塞队列 一.什么是阻塞队列? 二.阻塞队列有什么用? 三.阻塞队列的简单实用 [2]Java 线程池 一.我们为什么需要Java 线程池?使用它的好处是什么? 二.Java中主要提供了哪几种线程的线程池? 三.线程类的继承关系 四.ThreadPoolExecutor参数的含义 corePoolSize 五.线程池工作流程(机制) 六.关于两种提交方法的比较 [1]阻塞队列 一.什么是阻塞队列? ① 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满. ②

  • Java面试必备之AQS阻塞队列和条件队列

    一.AQS入队规则 我们仔细分析一下AQS是如何维护阻塞队列的,在独占方式获取资源的时候,是怎么将竞争锁失败的线程丢到阻塞队列中的呢? 我们看看acquire方法,这里首先会调用子类实现的tryAcquire方法尝试修改state,修改失败的话,说明线程竞争锁失败,于是会走到后面的这个条件: 这个addWaiter方法就是将当前线程封装成一个Node.EXCLUSIVE类型的节点,然后丢到阻塞队列中: 第一次还没有阻塞队列的时候,会到enq方法里面,我们仔细看看enq方法 enq()方法中,我们

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

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

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

    目录 队列 阻塞队列 ArrayBlockingQueue 重要属性 构造方法 添加元素 add(e) offer(e) put(e) offer(e,time,unit) 移除元素 take() dequeue() LinkedBlockingQueue 重要属性 构造方法 添加元素 offer(e) put(e) 移除元素 poll() take() 对比 总结 大家好,我是小黑,一个在互联网苟且偷生的农民工. 队列 学过数据结构的同学应该都知道,队列是数据结构中一种特殊的线性表结构,和平时

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

    目录 1. 什么是阻塞队列 2. 阻塞队列的代码使用 3. 生产者消费者模型 (1)应用一:解耦合 (2)应用二:削峰填谷 (3)相关代码 4.阻塞队列和生产者消费者模型功能的实现 1. 什么是阻塞队列 阻塞队列是一种特殊的队列,和数据结构中普通的队列一样,也遵守先进先出的原则同时,阻塞队列是一种能保证线程安全的数据结构,并且具有以下两种特性:当队列满的时候,继续向队列中插入元素就会让队列阻塞,直到有其他线程从队列中取走元素:当队列为空的时候,继续出队列也会让队列阻塞,直到有其他线程往队列中插入

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

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

  • java阻塞队列实现原理及实例解析

    这篇文章主要介绍了java阻塞队列实现原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 阻塞队列与普通队列的不同在于.当队列是空的时候,从队列中获取元素的操作将会被阻塞,或者当队列满时,往队列里面添加元素将会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素.同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完

  • Java阻塞队列四组API介绍(小结)

    通过前面几篇文章的学习,我们已经知道了Java中的队列分为阻塞队列和非阻塞队列以及常用的七个阻塞队列.如下图: 本文来源:凯哥Java(kaigejava)讲解Java并发系列之阻塞队列教程.系列文章,欢迎大家从第一篇文章开始看起. 在查看以上七个队列的API的时候,我们可以很明显的看到以下四组API: add()/remove()/remove offer()/poll()/peek() put/take() offer(e,time,unit)/poll(time,unit). 分别对应的是

  • Java 阻塞队列BlockingQueue详解

    目录 一. 前言 二. 认识BlockingQueue 三.BlockingQueue的核心方法: 四.常见BlockingQueue 五. 小结 一. 前言 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利.本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景. 二. 认识BlockingQueue 阻塞队列,

  • Java阻塞队列BlockingQueue详解

    目录 队列的类型 数据结构 阻塞队列 BlockingQueue 常见的阻塞队列 BlockingQueue API ArrayBlockingQueue 源码简解 生产者消费者模式 延迟队列 DelayQueue 队列的类型 无限队列(unbounded queue) 无容量限定,只随存储变化 有限队列(bounded queue) 定义了最大容量 向无限队列添加元素的所有操作都将永远不会阻塞(也是线程安全的),因此它可以增长到非常大的容量. 使用无限阻塞队列 BlockingQueue 设计

  • 详解Java阻塞队列(BlockingQueue)的实现原理

    阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满:从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空.并发包下很多高级同步类的实现都是基于BlockingQueue实现的. BlockingQueue 的操作方法 BlockingQueue 具有 4 组不同的方法用于插入.移除以及对队列中的元素进行检查.如果

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

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

  • 详解java中的阻塞队列

    阻塞队列简介 阻塞队列(BlockingQueue)首先是一个支持先进先出的队列,与普通的队列完全相同: 其次是一个支持阻塞操作的队列,即: 当队列满时,会阻塞执行插入操作的线程,直到队列不满. 当队列为空时,会阻塞执行获取操作的线程,直到队列不为空. 阻塞队列用在多线程的场景下,因此阻塞队列使用了锁机制来保证同步,这里使用的可重入锁: 而对于阻塞与唤醒机制则有与锁绑定的Condition实现 应用场景:生产者消费者模式 java中的阻塞队列 java中的阻塞队列根据容量可以分为有界队列和无界队

  • Java常见的阻塞队列总结

    Java阻塞队列 阻塞队列和普通队列主要区别在阻塞二字: 阻塞添加:队列已满时,添加元素线程会阻塞,直到队列不满时才唤醒线程执行添加操作 阻塞删除:队列元素为空时,删除元素线程会阻塞,直到队列不为空再执行删除操作 常见的阻塞队列有 LinkedBlockingQueue 和 ArrayBlockingQueue,其中它们都实现 BlockingQueue 接口,该接口定义了阻塞队列需实现的核心方法: public interface BlockingQueue<E> extends Queue

随机推荐