Java多线程编程(四)- 阻塞队列,生产者消费者模型,线程池

目录:

 一.阻塞队列 

二.线程池

 一.阻塞队列 

 1.阻塞队列是⼀种特殊的队列. 也遵守 "先进先出" 的原则 

阻塞队列能是⼀种线程安全的数据结构, 并且具有以下特性:
1.1.当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素 
1.2.队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素 
 

阻塞队列最主要的应用场景--->生产者消费者模型

2.生产者消费者模型的优点:

解耦合:

两个线程或者两个服务器之间如果直接访问,那么他们的耦合度就会很高,这个时候如果加入阻塞队列,让这两个服务器分别和这个阻塞队列交互,就会达到解耦合的目的 

削峰填谷: 

 阻塞队列的重要程度不小,所以也会直接会搞成几个服务器 

 


缺点: 

(1).效率会降低

(2).部署更多服务器生产环境复杂,管理起来比较麻烦


3.生产者消费者模型的实现:

自己实现一个阻塞队列,实现生产者消费者模型 

代码: 

class MyBlockingQueue{

    private String[] data = null;

    private int head = 0;//队首
    private int tail = 0;//队尾
    private int size = 0;//元素个数

    MyBlockingQueue(int capacity){
        data = new String[capacity];
    }

    //入队列
    public void put(String elem) throws InterruptedException {
        synchronized (this) {
            while (size >= data.length) {
                //阻塞
                this.wait();//队列不满时唤醒,其他线程take
            }
            data[tail] = elem;

//        tail = (tail+1) % data.length;

            tail++;
            if (size >= data.length) {
                tail = 0;
            }

            size++;
            this.notify();
        }

    }

    public String take() throws InterruptedException {

        synchronized (this) {
            while (size == 0) {
                //阻塞
                this.wait();//队列不空时唤醒,其他线程put
            }
            String ret = data[head];
            head++;
            if (size >= data.length) {
                head = 0;
            }

//        head = (head+1) % data.length;

            size--;
            this.notify();
            return ret;
        }
    }

}

使用: 至少一个生产者线程,一个消费者线程

 public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue(100);

        Thread producer = new Thread(()->{
            int n = 0;
            while (true){
                try {
                    queue.put(n + "");
                    System.out.println("生产元素 " + n);
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                n++;
            }
        },"producer ");


        Thread consumer = new Thread(()->{

            while (true){
                try {
                    String n = queue.take();
                    System.out.println("消费元素 " + n);
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"consumer ");


        producer.start();
        consumer.start();
    }

现象:这里Sleep加在生产者哪,导致产生的慢,队列可能为空阻塞,导致生产一个消费一个 

  


4.标准库中的阻塞队列: 
在 Java 标准库中内置了阻塞队列. 如果我们需要在⼀些程序中使⽤阻塞队列, 直接使⽤标准库中的即可.
BlockingQueue 是⼀个接口. 真正实现的类是 有链表类型LinkedBlockingQueue.
也有循环数组类型ArrayBlockingQueue

注意:
put 方法用于阻塞式的⼊队列, take ⽤于阻塞式的出队列.
BlockingQueue 也有 offer, poll, peek 等⽅法, 但是这些方法不带有阻塞特性 
 
public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();

        Thread producer = new Thread(()->{
            int n = 0;
            while (true){
                try {
                    queue.put(n);
                    System.out.println("生产元素 " + n);
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                n++;
            }
        },"producer ");


        Thread consumer = new Thread(()->{

            while (true){
                try {
                    Integer n = queue.take();
                    System.out.println("消费元素 " + n);
                    Thread.sleep(1000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"consumer ");


        producer.start();
        consumer.start();
    }


二.线程池 :

1.线程是什么?

里面有大量的线程可以直接给我们使用,不用系统去创建让我们高效的创建销毁线程,就和常量池类似,在Java程序构建时候准备好,等程序运行时直接加载到内存中使用 


2.为什么要有线程池?

就是让我们高效的创建销毁线程,就像创建进程太慢了引入线程一样的道理。 


3. 为什么直接从线程池取线程比创建线程快?

因为操作中唯一,一份内核要为很多应用程序提供服务,相当于创建线程交给了操作系统,这是不可控的 (如果要创建线程A,而操作系统有很多线程,创建分配资源是不可控的,可能很晚才轮到A,所以还不如,通过代码的方式创建好线程这样就可控且高效) 

4.Java标准库中的线程池: 

ThreadPoolExecutor里有一些线程可以让这些线程执行任务主要涉及的方法有:

核心方法submit(Runnable),  

Runnable描述一段要执行的任务

submit把任务放到线程池中执行 


构造ThreadPoolExecutor这个类构造方法最多有7个

 接下来我们来分析这7个参数


注:Java线程池中有几个线程任务多时自动创建出多个线程,任务少时会销毁 

corePoolSize:为核心线程数,核心线程在线程池创建时就创建,一直到线程池销毁才会跟着销毁

maximumPoolSize: 为最大线程数 (核心线程+非核心线程)非核心线程就是上面说的线程任务多时自动创建出多个线程,任务少时会销毁 


keepAliveTime: 非核心线程允许存活的最大时间,(非核心线程不是任务少时立即销毁,会有一段存活时间) 

 

unit: 定义一些枚举,比如上面线程存活时间,的时间


workQueue:工作队列(阻塞队列)(线程池的本质也是,生产者消费者模型,调用submit就是在生产任务,线程池则消费任务,线程池相当于消费者) 


threadFactory: 工厂模式(工厂模式也属于一种设计模式),弥补构造方法的缺陷,但是在构造方法上有时候无法重载,如果必须要系统类型的参数时

  

这个时候就可以单独封装一个工厂类去实现构造,用静态的方法,把构造对象过程和各种属性初始化封装起来。

class PointFactory {
    public static Point makePointByXY(double x, double y) {
        Point p = new Point();
        // 通过 x 和 y 给 p 进行属性设置
        return p;
    }

    public static Point makePointByRA(double r, double a) {
        Point p = new Point();
        // 通过 r 和 a 给 p 进行属性设置
        return p;
    }
}

handler: 拒绝策略,submit方法把任务添加到阻塞队列中不会阻塞(阻塞会影响客户端的体验),而是使用拒绝策略 ;具体怎么拒绝还要引入四个类说明: 

(1).AbortPolicy: 线程池直接抛出异常,可能导致线程池无法继续工作。

(2).CallerRunsPolicy: 让调用submit方法的线程自己去执行任务

(submit方法里可能先判断队列是否满,如果满了再判断是否执行CallerRunsPolicy策略,如果要执行,就调用里面的Runnable.run;让调用者线程自己去执行任务)

(3).DiscardOldesPolicy: 丢弃队列中最老的任务

(4).DiscardPolicy: 丢弃队列中最新的任务(当前submit这个任务)


5.Java标准库也提供了另外一组类Executors,对ThreadPoolExecutor这个类进一步封装,来简化线程池的使用,由于被进一步封装线程数目和拒绝策略是隐式的,规模大业务多的公司不好控制,所以阿里巴巴编程规范手册里,不推荐使用,但是大多数公司还是支持的具体看公司规范。

newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数⽬动态增⻓的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池
 

6.自己实现线程池:

线程池相当于消费者,消费阻塞队列里的任务,线程池的实现也是基于生产者消费者模型的: 

package demo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 苏李涛
 * Date: 2024-10-22
 * Time: 16:48
 */

// 实现一个固定线程个数的线程池

class MyThreadPool{
    BlockingQueue<Runnable> queue = null;

    MyThreadPool(int n){

        // 初始化线程池,创建固定个数的线程
        // 这里使用ArrayBlockingQueue作为任务队列, 容量为1000

        queue = new ArrayBlockingQueue<>(1000);

        // 创建 N 个线程
        for(int i = 0; i < n; i++) {

            Thread t = new Thread(()->{

               try {
                   while (true){
                       Runnable task = queue.take();
                       task.run();
                   }
               }catch (InterruptedException e){
                   e.printStackTrace();
               }
            });

            t.setDaemon(true);//把线程设置为后台线程,方便结束,注意:此方法必须在线程启动之前调用
            t.start();
        }
    }


    public void submit(Runnable task){
        task.run();
    }

}

public class Demo1 {
    public static void main(String[] args) {
        MyThreadPool pool = new MyThreadPool(10);

        // 向线程池提交任务
        for (int i = 0; i < 100; i++) {
            int id = i;
            pool.submit(()->{
                System.out.println(Thread.currentThread().getName() +" "+ "id=" + id);
            });
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/913066.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度剖析JUC中LongAdder类源码

文章目录 1.诞生背景2.LongAdder核心思想3.底层实现&#xff1a;4.额外补充 1.诞生背景 LongAdder是JDK8新增的一个原子操作类&#xff0c;和AtomicLong扮演者同样的角色&#xff0c;由于采用AtomicLong 保证多线程数据同步&#xff0c;高并发场景下会导致大量线程同时竞争更新…

大数据面试题--kafka夺命连环问

1、kafka消息发送的流程&#xff1f; 在消息发送过程中涉及到两个线程&#xff1a;一个是 main 线程和一个 sender 线程。在 main 线程中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给双端队列&#xff0c;sender 线程不断从双端队列 RecordAccumulator 中拉取…

树形结构数据

树形结构数据 树形结构数据是一种基础且强大的数据结构&#xff0c;广泛应用于计算机科学和软件开发的各个领域。它模拟了自然界中树的层级关系&#xff0c;通过节点和它们之间的连接来组织数据。在本文中&#xff0c;我们将深入探讨树形结构数据的概念、特点、类型以及它们在…

dell服务器安装ESXI8

1.下载镜像在官网 2.打开ipmi&#xff08;idrac&#xff09;&#xff0c;将esxi镜像挂载&#xff0c;然后服务器开机 3.进入bios设置cpu虚拟化开启&#xff0c;进入boot设置启动选项为映像方式 4..进入安装引导界面3.加载完配置进入安装 系统提示点击继 5.选择安装磁盘进行…

信息安全数学基础(46)域和Galois理论

域详述 定义&#xff1a; 域是一个包含加法、减法、乘法和除法&#xff08;除数不为零&#xff09;的代数结构&#xff0c;其中加法和乘法满足交换律、结合律&#xff0c;并且乘法对加法满足分配律。同时&#xff0c;域中的元素&#xff08;通常称为数&#xff09;在加法和乘法…

Windows端口占用/Java程序启动失败-进程占用的问题解决

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Python酷库之旅-第三方库Pandas(204)

目录 一、用法精讲 951、pandas.IntervalIndex.values属性 951-1、语法 951-2、参数 951-3、功能 951-4、返回值 951-5、说明 951-6、用法 951-6-1、数据准备 951-6-2、代码示例 951-6-3、结果输出 952、pandas.IntervalIndex.from_arrays类方法 952-1、语法 952…

AndroidStudio-文本显示

一、设置文本的内容 1.方式&#xff1a; &#xff08;1&#xff09;在XML文件中通过属性&#xff1a;android:text设置文本 例如&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.andr…

微星爆破弹ddr4wifi接线梳理研究

主板(微星爆破弹ddr4 wifi) mac用久了&#xff0c;windows的键盘都有点不习惯了。 理清了这些接口都是干啥的&#xff0c;接线就非常简单了。

机器视觉基础—双目相机

机器视觉基础—双目相机与立体视觉 双目相机概念与测量原理 我们多视几何的基础就在于是需要不同的相机拍摄的同一个物体的视场是由重合的区域的。通过下面的这种几何模型的目的是要得到估计物体的长度&#xff0c;或者说是离这个相机的距离。&#xff08;深度信息&#xff09…

【GPTs】EmojiAI:轻松生成趣味表情翻译

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;EmojiAI主要功能适用场景优点缺点 &#x1f4af;小结 &#x1f4af;GPTs指令 中文翻译&#xff1a; 此 GPT 的主要角色是为英文文本提供幽默…

「C/C++」C/C++STL 之 push_back 和 emplace_back 的区别

✨博客主页何曾参静谧的博客📌文章专栏「C/C++」C/C++程序设计📚全部专栏「VS」Visual Studio「C/C++」C/C++程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid函数说明目…

【Golang】Go语言教程

Go语言教程 文章目录 Go语言教程一、Go语言教程二、Go语言特色三、Go语言用途四、第一个Go程序六、运行代码的两种方式七、go run和go buil的区别7.1、go run7.2、Go build 一、Go语言教程 Go全称Golang Go是一个开源的编程语言&#xff0c;它能让构造简单、可靠且高效的软件变…

揭秘云计算 | 2、业务需求推动IT发展

揭秘云计算 | 1、云从哪里来&#xff1f;-CSDN博客https://blog.csdn.net/Ultipa/article/details/143430941?spm1001.2014.3001.5502 书接上文&#xff1a; 过去几十年间IT行业从大型主机过渡到客户端/服务器&#xff0c;再过渡到现如今的万物互联&#xff0c;IT可把控的资…

Tencent Hunyuan3D

一、前言 腾讯于2024年11月5日正式开源了最新的MoE模型“混元Large”以及混元3D生成大模型“Hunyuan3D-1.0”&#xff0c;支持企业及开发者在精调、部署等不同场景下的使用需求。 GitHub - Tencent/Hunyuan3D-1 二、技术与原理 Hunyuan3D-1.0 是一款支持文本生成3D&#xff08;…

WPF在MVVM模式下怎么实现导航功能

在mvvm的模式下wpf通过frame实现页面跳转_哔哩哔哩_bilibili 视频讲解同步可观看 如下图&#xff0c;我们要实现点击左侧的菜单&#xff0c;在右侧展示不同的页面 实现代码如下&#xff1a; 一、如何从主窗体跳转到页面。 1、在mainwindow.xaml的菜单栏代码里加入如下代码 …

SpringBoot整合Sharding-JDBC实现读写分离

SpringBoot整合Sharding-JDBC实现读写分离 Sharding-JDBC实现读写分离&#xff0c;记得先要实现数据库的主从结构先。 1、Sharding-JDBC 简介 Sharding-JDBC 是的分布式数据库中间件解决方案。Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar(计划 中)是 3 款相互独立的…

洛谷每日一题——P1036 [NOIP2002 普及组] 选数、P1045 [NOIP2003 普及组] 麦森数(高精度快速幂)

P1036 [NOIP2002 普及组] 选数 题目描述 [NOIP2002 普及组] 选数 - 洛谷 运行代码 #include <stdio.h> int n, k, a[25], t; int ss(int b) {int i;if (b < 2)return 0;for (i 2; i * i < b; i)if (b % i 0)return 0;return 1; } void dfs(int num, int sum, …

从零开始 blender插件开发

blender 插件开发 文章目录 blender 插件开发环境配置1. 偏好设置中开启相关功能2. 命令行打开运行脚本 API学习专有名词1. bpy.data 从当前打开的blend file中&#xff0c;加载数据。2. bpy.context 可用于获取活动对象、场景、工具设置以及许多其他属性。3. bpy.ops 用户通常…