理解Java并发工具包线程池的设计
深度解读 java 线程池设计思想及源码实现

分布式锁unlock 问题产生原因分析:

Step 1 :线程A先上同一个锁(Key)(20秒), 然后执行耗时业务,业务太长时间的执行(30秒,琐失效);
与此同时,
Step 2 :过了20秒 ,琐失效, 线程B获取到该锁进行业务,A执行任务之后去释放该锁(锁被B持有了)
报 Exception in thread “thread-2” java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 9f178836-f7e1-44fe-a89d-2db52f399c0d thread-id: 21
at org.redisson.RedissonLock.unlock(RedissonLock.java:353) ,

为什么uat会出现,生产没出现呢?

   原因可能是uat 经常发版,生产执行很快,20秒的锁时间应该够了

第0个问题:怎么去运用线程池?工作中如何使用?

工作中,我们有时候需要实现一些耗时的任务,例如 会有,将Word转换成PDF存储. 的 需求

1 没使用线程池的实现代码–每次都开启新的线程,会导致 资源耗尽,系统宕机

import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadVs {/*** 老的处理方式*/@Testpublic void oldHandle() throws InterruptedException {/*** 使用循环来模拟许多用户请求的场景*/for (int request = 1; request <= 100; request++) {new Thread(() -> {System.out.println("文档处理开始!");try {// 将Word转换为PDF格式:处理时长很长的耗时过程Thread.sleep(1000L * 30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("文档处理结束!");}).start();}Thread.sleep(1000L * 1000);}
}

2 使用线程池的实现代码

 /*** 新的处理方式*/@Testpublic void newHandle() throws InterruptedException {/*** 开启了一个线程池:线程个数是10个*/ExecutorService threadPool =Executors.newFixedThreadPool(10);/*** 使用循环来模拟许多用户请求的场景*/for (int request = 1; request <= 100; request++) {threadPool.execute(() -> {System.out.println("文档处理开始!");try {// 将Word转换为PDF格式:处理时长很长的耗时过程Thread.sleep(1000L * 30);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("文档处理结束!");});}Thread.sleep(1000L * 1000);}

首先,我们做一件事情之前,总会有三个关键性的问题?

  1. 定义问题:是什么
  2. 问题的意义:为什么
  3. 解决问题:怎么做

第一个问题: 线程池是什么?

线程池顾名思义就是事先创建若干个可执行的线程放入一个池中(容器),需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。任何池化技术都是减低资源消耗,例如我们常用的 数据库连接池

第2个问题: 为什么要用线程池?

降低资源消耗
提高响应速度
提高线程的可管理性

如果我们不使用已有的线程池, 而是我们自己去设计的话, 怎么去设计一个线程池?简单线程池设计?

设计过程中我们需要思考的问题

  1. 初始创建多少线程?
  2. 没有可用线程了怎么办?
  3. 缓冲数组需要设计多长?
  4. 缓冲数组满了怎么办?

第一次需求分析:简陋版本
下图是最简陋的线程池版本:具有的功能有

  1. 客户端获取线程
  2. 客户端归还线程
  3. 开启线程池,初始化线程池,关闭线程池

该设计方案,如下图所示,我们需要考虑下面几个问题:
1.在获取线程的时候,线程池没有线程可以获取的情况怎么处理?
2.初始化线程池时候,初始化多少个线程才算合适?
3.对于客户端使用不够方便,使用之后还要归还线程?不好使用

第二次需求分析:改进版本

改进版依然需要解决的三个问题

  1. 任务队列多长才好
  2. 队列满了之后怎么办?应该采取什么策略
  3. 线程池初始化,初始化多少线程才合适?
    设计之前参考巨人的设计

corePoolSize 核心线程数量
maximumPoolSize 最大线程数量
keepAliveTime 线程空闲后的存活时间(没有任务后)
unit 时间单位
workQueue 用于存放任务的阻塞队列
threadFactory 线程工厂类
handler 当队列和最大线程池都满了之后的饱和策略

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();// 这几个参数都是必须要有的if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;

java 源码里面设计的线程池的处理流程

注意1个问题:

  1. 阻塞队列未满,是不会创建新的线程的

线程池可选择的阻塞队列

插入移除操作: 插入操作和移除操作

  1. 无界队列:无限长的队列阻塞队列,可以一直往里面追加元素 LinkedBlockingQueue
  2. 有界队列:有界限的阻塞队列,ArrayBlockingQueue
  3. 同步移交队列:不存储元素的阻塞队列,每个插入的操作必须等待另外一个线程取出元素,SynchronousQueue ,消费者生产者缓冲作用,RocketMQ

下面是三种阻塞队列的Java代码实现

import org.junit.Test;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;public class QueueTest {@Testpublic void arrayBlockingQueue() throws InterruptedException {/*** 基于数组的有界阻塞队列,队列容量为10*/ArrayBlockingQueue queue =new ArrayBlockingQueue<Integer>(10);// 循环向队列添加元素for (int i = 0; i < 20; i++) {queue.put(i);System.out.println("向队列中添加值:" + i);}}@Testpublic void linkedBlockingQueue() throws InterruptedException {/*** 基于链表的有界/无界阻塞队列,队列容量为10*/LinkedBlockingQueue queue =new LinkedBlockingQueue<Integer>();// 循环向队列添加元素for (int i = 0; i < 20; i++) {queue.put(i);System.out.println("向队列中添加值:" + i);}}@Testpublic void test() throws InterruptedException {/*** 同步移交阻塞队列*/SynchronousQueue queue = new SynchronousQueue<Integer>();// 插入值new Thread(() -> {try {queue.put(1);System.out.println("插入成功");} catch (InterruptedException e) {e.printStackTrace();}}).start();// 删除值/*new Thread(() -> {try {queue.take();System.out.println("删除成功");} catch (InterruptedException e) {e.printStackTrace();}}).start();*/Thread.sleep(1000L * 60);}}

线程池可选择的饱和策略
当阻塞队列满和最大线程数满了的时候,饱和策略就会发挥作用

  1. AbortPolicy 终止策略(默认): 通过抛出异常
  2. DiscardPolicy : 丢弃策略 : 什么都不做
  3. DiscardOldestPolicy : 丢弃旧任务策略: 丢弃最久的任务,执行当前任务
  4. CallerRunsPolicy : 调用者自运行策略:调用方自己执行自己的任务

线程池的执行示意图

  1. 第一步:主线程调用execute()方法来执行一个线程任务
  2. 第二步:如果核心线程池没有满,会立即创建新的线程来执行任务,如果核心线程池已经满了,则会调用方法2
  3. 第三步:当阻塞队列也和核心线程都满了之后,会执行方法3,从最大线程池数量里面获取线程,前提是不超过最大线程数
  4. 第四步:如果方法3也没法走通,接着执行方法4,执行饱和策略
  5. 第5步:如果饱和策略是 CallerRunsPolicy , 交给主线程自己去运行任务的run方法

常用线程池

  1. newCachedThreadPool 线程数量无限大的,同步移交队列的 线程池
 /*** Creates a thread pool that creates new threads as needed, but* will reuse previously constructed threads when they are* available.  These pools will typically improve the performance* of programs that execute many short-lived asynchronous tasks.* Calls to {@code execute} will reuse previously constructed* threads if available. If no existing thread is available, a new* thread will be created and added to the pool. Threads that have* not been used for sixty seconds are terminated and removed from* the cache. Thus, a pool that remains idle for long enough will* not consume any resources. Note that pools with similar* properties but different details (for example, timeout parameters)* may be created using {@link ThreadPoolExecutor} constructors.** @return the newly created thread pool*/// 线程数量无限大的线程池,需要小心public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
 *创建一个线程池,该线程池根据需要创建新线程将重用先前构造的可用的线程。*这些池通常可以提高性能执行许多短暂的异步任务的程序。*调用{@code execute}将重用以前构造的线程(如果有)。* 如果没有现有线程可用,则新线程将被创建并添加到池中。*具有的线程*六十秒未使用将终止并从缓存中删除*因此,闲置足够长时间的池将不消耗任何资源。请注意,类似的池属性,*但细节不同(例如,超时参数)可以使用{@link ThreadPoolExecutor}构造函数创建。* @返回新创建的线程池
  1. newFixedThreadPool 线程数量固定,无界阻塞队列 的线程池
/**
* 线程数量固定的线程池
* nThreads 核心线程数和最大核心线程数
* LinkedBlockingQueue 无界阻塞队列,注意这个是无界的无限长的任务队列
*/public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
  1. newSingleThreadExecutor 线程数量只有1的无界阻塞队列 线程池
/**
* 单一线程的线程池
* LinkedBlockingQueue 无界阻塞队列,注意这个是无界的无限长的任务队列
*/public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

如何向线程池提交任务

向线程池提交任务的两种方式:

第一种: 利用submit方法提交任务,接收任务的返回结果

@Testpublic void submitTest()throws ExecutionException, InterruptedException {// 创建线程池ExecutorService threadPool =Executors.newCachedThreadPool();/*** 利用submit方法提交任务,接收任务的返回结果*/Future<Integer> future = threadPool.submit(() -> {Thread.sleep(1000L * 10);return 2 * 5;});/*** 阻塞方法,直到任务有返回值后,才向下执行*/Integer num = future.get();System.out.println("执行结果:" + num);}

第二种: 用execute方法提交任务,没有返回结果

@Testpublic void executeTest() throws InterruptedException {// 创建线程池ExecutorService threadPool =Executors.newCachedThreadPool();/*** 利用execute方法提交任务,没有返回结果*/threadPool.execute(() -> {try {Thread.sleep(1000L * 10);} catch (InterruptedException e) {e.printStackTrace();}Integer num = 2 * 5;System.out.println("执行结果:" + num);});Thread.sleep(1000L * 1000);}

线程池的状态

一个有趣的问题 : 如何设计一个线程池相关推荐

  1. Java黑皮书课后题第9章:*9.11(代数:2*2的线性方程)为一个2*2的线性方程设计一个名为LinearEquation的类

    Java黑皮书课后题第9章:*9.11(代数:2*2的线性方程)为一个2*2的线性方程设计一个名为LinearEquation的类 题目 破题 代码 Test10 Test11_LinearEquat ...

  2. 从架构设计看线程池,无源码分析

    线程池参数 /*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters.** @param cor ...

  3. MySQL设计一个图书馆数据库_请设计一个图书馆数据库

    匿名用户 1级 2014-05-05 回答 原文出处]现代图书情报技术 京 200206 4-6 G9 图书馆学.信息科学.资料工作 200301 基于UML的高校图书馆管理系统 The Applic ...

  4. 用html设计一个logo页面_如何设计一个Logo?——Bobu Africa旅行品牌Logo设计

    负空间Logo听起来很牛逼,但是到底要怎么做? Bobu Africa是一家位于肯尼亚,主营泛非洲奢侈旅行与工艺品销售的品牌.Africa当然指的是其主要业务范围--非洲.Bobu则是猴面包树Baob ...

  5. java如何创建一个dao类_java – 如何设计一个DAO类?

    应该是什么是设计DAO类的最佳方式? 方法#1:将DAO类设计为对象. class Customer { //customer class } class CustomerDAO { public v ...

  6. 使用VC实现一个“智能”自增减线程池

    工作中接手了一款产品的改造.因为该产品可能使用很多线程,所以产品中使用了线程池.(转载请指明来自BreakSoftware的CSDN博客) 线程池的一个优点是降低线程创建和销毁的频率:缺点是可能在比较 ...

  7. 随笔之如何实现一个线程池

    为什么80%的码农都做不了架构师?>>>    一 缘由:     最近因工作问题,需要实现一个简单的线程池,满足一下要求, 可伸缩,即一旦发现线程不够用,则可以动态增加线程.(至于 ...

  8. 一个有趣的实验:用0.1f 替换 0,性能提升 7 倍!

    点击关注上方"视学算法",设为"置顶或星标",第一时间送达技术干货. 本文来源:http://cenalulu.github.io/linux/about-de ...

  9. Windows下一个比较完美的线程池实现

    1.  前言 线程池不是一个新鲜的东西,网上能找到很多原理.实现,甚至很多库都提供了实现,比如微软的 ATL::CThreadPool, Vista后提供的CreateThreadpoolWork, ...

最新文章

  1. JAVA大一新生要用电脑吗,大一新生有没有必要买电脑?辅导员:倘若不是这三点原因尽量别买...
  2. 隐马尔可夫模型(Hidden Markov Model,HMM)是什么?隐马尔可夫模型(Hidden Markov Model,HMM)的三个基本问题又是什么?
  3. 统计空格流程图、火车组成jackson图
  4. centos7 docker升级到最新稳定版本
  5. 《Unreal Engine 4蓝图可视化编程》一1.6 改变目标方向
  6. 报名|极市X机器之心 2018计算机视觉最具潜力开发者榜单
  7. 关于pytorch中super的一点思考,结合代码
  8. 他在 B 站有 178 万粉丝,今天免费带你玩转 Python
  9. 精妙SQL语句【转】
  10. MD5算法原理与常用实现
  11. PSP(Python Server Pages) 快速实例
  12. 《跟李沐读论文》之对比学习
  13. java batik_Java Batik框架画SVG图 JSVGCanvas
  14. PHP开发工具phpDesigner 7 (最新版,含注册机)
  15. VelocityTracker 使用
  16. 数据库性能优化的五种方案(mycat,基于阿里coba开源的数据库中间件,很容易实现分库分表、主从切换功能。另一个当当网开源的一个库 sharding-jdbc)
  17. html清除iframe的缓存,如何清除iFrame的缓存?
  18. 硬盘是计算机的 奥鹏,奥鹏2016计算机应用基础一
  19. U盘中文件消失但仍占用空间
  20. 利用普普通通的游戏引擎实现普普通通的电梯调度算法

热门文章

  1. Docker基本命令入门
  2. 程序代码移植和烧录需要注意什么_法人变更需要注意什么
  3. vue —— vuex namespaced模块化编码
  4. Java通过引用操作对象的“共享”特性
  5. pythonqueue线程_python之线程queue
  6. 为什么说Pravega是流处理统一批处理的最后一块拼图?
  7. 【Processing日常2】群星1
  8. iOS--百度地图相关功能的实现
  9. 《Python分布式计算》 第8章 继续学习 (Distributed Computing with Python)
  10. leetcode最长递增子序列问题