IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 多线程实现(二)——线程池实现多线程 -> 正文阅读

[Java知识库]多线程实现(二)——线程池实现多线程

系列文章目录

多线程实现(一)——概念和三种实现多线程实操
多线程实现(二)——线程池实现多线程



前言

上一篇文章讲了线程的三种实现 方式,但在实际项目开发中,基本用不到找这些方法,而通常用线程池来实现,为什么这样子呢?是因为我们继承Thread、实现Runable、实现FutureTask这三种线程方法无法控制线程,可能造成线系统资源浪费,性能下降;所以必须使用线程池,构建多线程;让线程充分利用,降低系统的资源消耗。(就像救济站发放免费的食物,对前来领取的人不限制,结果大家一窝蜂全来了,本来只能容纳30人的房间,瞬间撑爆。星爷的电影《破坏之王》有一个片段是排队买学友的电影票,结果阿婆一个人直接买光了,场面一度混乱哈哈哈)
在这里插入图片描述


拓展:
问题:执行一个java任务,可以直接new Thread来运行任务,线程从创建到消耗都经历了哪些过程?
答:①创建java线程实例,线程也是一个对象实例(像new),堆内存中分配内存(需要消耗时间和内存);
②执行start方式启动(操作系统为java线程创建对应的内核线程,线程处于就绪状态。需要时间和内存)
③(内核)线程被操作系统CPU调度选中后,线程开始执行(run方法开始运行)
④JVM开始为线程创建线程私有资源:JVM虚拟机栈*程序计数器(需要时间和内存)
⑤线程运行过程中,cpu上下文切换(消耗时间,频繁切换,影响性能)
⑥线程运行完毕,java线程被垃圾回收器回收(销毁内存需要时间)
总结:
线程不仅是java对象,更是操作系统的资源(创建和消耗线程都需要时间);
java线程的创建和运行都需要内存空间(线程数量太多,消耗很多内存);
cpu上下文换(线程数量一大,cpu频繁切换);


提示:以下是本篇文章正文内容,下面案例可供参考

一、线程池的优势有哪些?

降低系统资源的消耗;提供系统的响应速度;方便管理(线程复用,控制最大并发数,管理线程);
即事先创建好一些资源,有人要用(类比业务系统需要用线程),就来我这里拿(线程池),用完之后不销毁必须还给我(线程池的复用)

二、线程池实现多线程实践

2.1、第一种: 创建线程池对象;创建单个线程的线程池对象

代码如下(示例):

public class ThreadPool {
    public static void main(String[] args) {
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            //线程执行
            try{
                executorService.execute(()->{
                    System.out.println("使用executor方式实现多线程.....");
                    //业务代码
                    int i = 99 / 3;
                    System.out.println("业务代码执行结果:" + i);
                });
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                //线程池用完,关闭线程池
                executorService.shutdown();
            }
    }
}

2.2、第二种: 创建固定数量的线程池(指定核心线程数数量)

代码如下(示例):

public class ThreadPool {
    public static void main(String[] args) {
            ExecutorService executorService2 = Executors.newFixedThreadPool(2);
            //线程执行
            try{
                executorService2.execute(()->{
                    System.out.println("使用executor方式实现多线程.....");
                    //业务代码
                    int i = 99 / 3;
                    System.out.println("业务代码执行结果:" + i);
                });
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                //线程池用完,关闭线程池
                executorService2.shutdown();
            }
    }
}

2.3、第三种:创建一个按照计划执行的线程池

代码如下(示例):

public class ThreadPool {
    public static void main(String[] args) {
            ScheduledExecutorService executorService3 = Executors.newScheduledThreadPool(2);
            //线程执行
            try{
                executorService3.execute(()->{
                    System.out.println("使用executor方式实现多线程.....");
                    //业务代码
                    int i = 99 / 3;
                    System.out.println("业务代码执行结果:" + i);
                });
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                //线程池用完,关闭线程池
                executorService3.shutdown();
            }
    }
}

2.4、第四种:创建一个自动增长的线程池

代码如下(示例):

public class ThreadPool {
    public static void main(String[] args) {
            ExecutorService executorService4 = Executors.newCachedThreadPool();
            //线程执行
            try{
                executorService4.execute(()->{
                    System.out.println("使用executor方式实现多线程.....");
                    //业务代码
                    int i = 99 / 3;
                    System.out.println("业务代码执行结果:" + i);
                });
            }catch(Exception e){
                e.printStackTrace();
            }finally{
                //线程池用完,关闭线程池
                executorService4.shutdown();
            }
    }
}

上面四种创建方式仅用于学习。不建议在项目中使用。原因是:当线程量一大后,可能造成无限制创建线程,导致内存被占满,线程量大导致性能严重下降,甚至OOM;


2.5、使用 ThreadPoolExecutor 类创建线程池(解决无限制创建线程,推荐项目中使用)

代码如下(示例):

public ThreadPoolExecutor(
	int corePoolSize,
	int maximumPoolSize,
	long keepAliveTime,
	TimeUnit unit,
	BlockingQueue<Runnable> workQueue,
	ThreadFactory threadFactory,
	RejectedExecutionHandler handler)

参数解析:
1、corePoolSize: 线程池核心线程数,初始化线程池时候,会创建
核心线程等待状态,核心线程不会被销毁,提供线程的复用;
2、maximumPoolSize: 最大线程数;核心线程用完了,必须新建线程
执行任务,但是新建的线程不能超过最大线程数;
3、keepAliveTime:线程存活时间,除了核心线程以为
(maximumPoolSize- corePoolSize)的线程存活时间;当线程处于空闲状态,他可以活多久;
4、unit: 存活时间单位
5、workQueue:任务阻塞队列,任务可能会很多,线程就那么几个,
因此可以把多余的任务放入队列进行缓冲,队列采用 FIFO 的,等待线程空闲,再从队列取出任务执行;
6、threadFactory: 线程工厂,默认使用 defaultthreadFactory, 用来创建线程的,一般使用默认即可;
7、RejectedExecutionHandler: 线程池拒绝策略
四种拒绝策略:
(1)、AbortPolicy : 新任务直接被拒绝,抛出异常:
(2)、DisCardPolicy: 队列满了,新任务忽略不执行,直接丢弃,不会抛出异常
(3)、DisCardOldestPolicy: 队列满了,新任务尝试和等待最久的线程竞争,也不会抛出异常;抛弃任务队列中等待最久任务,新任务直接添加到队列中
(4)、CallerRunPolicy: 新任务来临后,直接使用调用者所在线程执行任务即可

public class ThreadPool {
    public static void main(String[] args) {
        /**
         * !!! 使用此种线程池,不会有资源耗尽的风险
         * 合理配置相关参数: 【最大线程数设置公式:(任务执行时间/任务cpu时间)* N】
         *   1.cpu密集型任务(所有任务都在内存中执行,没有磁盘的读写)
         *    核心线程数:服务器cpu核心数
         *    最大线程数:cpu核心数+1
         *   2.IO密集型(有大量的磁盘读写任务,cpu出于空闲状态)
         *    核心线程数:cpu核心数
         *    最大线程数:2N+1
         *
         */
        //可伸缩的线程创建(更加可控)
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                Runtime.getRuntime().availableProcessors(), //线程池核心线程数(这里获取程序所在服务器的最大线程数)
                13, //最大个数要不小于核心线程数
                3,  //线程存活时间   
                TimeUnit.SECONDS,  //存活时间单位
                new LinkedBlockingQueue<>(3),  //队列可装,装满后队列可伸缩再装三个
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        try{
             for(int i = 0; i < 10; i++ ){
                threadPool.execute(()->{
                    System.out.println("使用executor方式实现多线程.....");
                    //业务代码
                    int j = 99 / 3;
                    System.out.println("业务代码执行结果:" + j);
                });
	      	 }
        }catch(Exception e){
                e.printStackTrace();
        }finally{
            //线程池用完,关闭线程池
            threadPool.shutdown();
        }
    }
}

总结

  1. 合理配置线程相关的参数: 核心线程数,最大线程数
    设置线程系线程数的数量: 根据业务类型进行设置(cpu 密集型,Io 密集型)
    如果 CPU 密集型任务(所有任务都在内存中执行:没有磁盘的读写): 建议线程池最大数量设置为 N(cpu 核心数量)+1
    如果 IO 密集型任务(大量磁盘读写任务):如果有 IO 操作,cpu 此时处于空闲状态, 最大线程数应该设置:2N+1
    最大线程数设置公式:最大线程数 = (任务执行时间 / 任务 cpu 时间)*N

  2. 参数workQueue队列的选择
    1)使用队列,可以让任务在队列中进行缓存;可以线程执行防洪泄流的效果,提升线程池的任务能力
    2)如何选择合适的队列?
    基于 Java 一些队列实现特性:
    1、ArrayBlockingQueue : 基于数组实现的有界的阻塞队列 (有界的队列)
    2、LinkedBlockingQueue: 基于链表实现的有界阻塞队列
    3、PriorityBlockingQueue: 支持按照优先级排序的无界的阻塞的队列
    4、DelayQueue: 优先级实现的无界阻塞队列
    5、SynchronousQueue : 只存储一个元素,如果这个元素没有被消费,不能在向里面存储元素
    6、LinkedTransferQueue: 基于链表实现的无界的阻塞队列
    7、LinkedBlockingDeque: 基于链表实现的双向的无界阻塞的队列
    如何选择一个合适的队列:建议必须使用有界队列。
    有界队列能增加系统的稳定性,根据需求设置大一些(可控设置); 如果设置为无界队列,
    遇到不可控的因素,可能会导致队列中的任务越来越多,出现 OOM,撑爆整个系统;

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-09-27 13:57:03  更:2021-09-27 13:58:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:44:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码