模拟Java线程池运行原理

  • 在了解线程池之前,我们先来谈谈线程的状态转换
    • 线程常用方法及种类
  • 线程池实现原理和线程池概念
    • 四种线程池
    • 线程池的组成
    • 代码
    • 自定义线程池功能简单

在了解线程池之前,我们先来谈谈线程的状态转换

一个线程到底有哪些状态呢?
1、新建状态(new):线程对象被创建后就进入了新建状态。例如 Thread t = new Thread();
2、就绪状态(Runnable):也可以称为可执行状态,线程对象被创建后,其他线程(主线程、守护线程、用户线程)调用了该对象的start()方法,从而启动该线程。如:t.start(); 处于就绪状态的线程随时可能被CPU调度执行。
3、运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
1)等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成。
2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
3)其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或因异常退出了run()方法,该线程结束生命周期。

线程常用方法及种类

线程的种类:
系统级线程:
核心级线程 由操作系统内核进行管理,使用户程序可以创建,执行 撤销
用户级线程:
管理过程全部都是由用户程序完成,操作系统内核只对进程管理
多线程有哪些优势?
多线程使系统空转时间减少 ----提高CPU的利用率
进程间不能共享内存 但是线程之间共享内存非常容易
使用多线程实现多任务高并发 比多进程的效率高
线程方法:
Thread.currendThead() 获取当前线程的引用
getName() 获取线程的名字
setName() 设置线程的名字
start() 启动线程
run() 存放线程体的代码
setDaemon() 将线程设置为守护线程
线程的优先级:
查看优先级的方法 getPriority()
每个线程都具有一定的优先级 当调度线程时 会优先考虑级别比较高的线程
默认情况下:
一个线程继承父类的线程的优先级,可以使用线程中的setPriority(int newPriority) 设置线程的优先级别。
优先级主要影响的是 CPU在线程间在切换状态下
原则:
当一个线程 通过显示放弃 睡眠 或者阻塞 自愿释放控制权时 所有的线程都要接收检查,然后优先级高的会优先执行。
一个线程可以被一个高优先级的线程抢占资源。 同级别的线程之间,通过控制权的释放确保所有的线程均有机会执行。

线程池实现原理和线程池概念


线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。在java的API中实现线程池的类超类是java.util.concurrent 包下的Executor接口。

执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用 Executor 而不是显式地创建线程。例如,可能会使用以下方法,而不是为一组任务中的每个任务调用 new Thread(new(RunnableTask())).start():
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
不过,Executor 接口并没有严格地要求执行是异步的。在最简单的情况下,执行程序可以在调用方的线程中立即运行已提交的任务:
class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}更常见的是,任务是在某个不是调用方线程的线程中执行的。以下执行程序将为每个任务生成一个新线程。
class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread®.start();
}
}

许多 Executor 实现都对调度任务的方式和时间强加了某种限制。以下执行程序使任务提交与第二个执行程序保持连续,这说明了一个复合执行程序。

class SerialExecutor implements Executor {final Queue<Runnable> tasks = new LinkedBlockingQueue<Runnable>();final Executor executor;Runnable active;SerialExecutor(Executor executor) {this.executor = executor;}public synchronized void execute(final Runnable r) {tasks.offer(new Runnable() {public void run() {try {r.run();} finally {scheduleNext();}}});if (active == null) {scheduleNext();}}protected synchronized void scheduleNext() {if ((active = tasks.poll()) != null) {executor.execute(active);}}}

此包中提供的 Executor 实现实现了 ExecutorService,这是一个使用更广泛的接口。ThreadPoolExecutor 类提供一个可扩展的线程池实现。Executors 类为这些 Executor 提供了便捷的工厂方法。

四种线程池

Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而
只是一个执行线程的工具。真正的线程池接口是 ExecutorService。

newCachedThreadPool:
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行
很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造
的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并
从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资
源。
newFixedThreadPool:
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大
多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,
则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何
线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之
前,池中的线程将一直存在。
newScheduledThreadPool:
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); scheduledThreadPool.schedule(newRunnable(){ @Override public void run() {System.out.println("延迟三秒");}}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ @Override public void run() {System.out.println("延迟 1 秒后每三秒执行一次");}},1,3,TimeUnit.SECONDS);

newSingleThreadExecutor:
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程
池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

线程池的组成

一般分为以下4个组成部分:

  1. 线程池管理器:用于创建并管理线程池
  2. 工作线程:线程池中的线程
  3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
  4. 任务队列:用于存放待处理的任务,提供一种缓冲机制
    Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 这几个类。

    ThreadPoolExecutor的构造方法:
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;}

参数介绍:

  1. corePoolSize:指定了线程池中的线程数量。
  2. maximumPoolSize:指定了线程池中的最大线程数量。
  3. keepAliveTime:当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多
    次时间内会被销毁。
  4. unit:keepAliveTime 的单位。
  5. workQueue:任务队列,被提交但尚未被执行的任务。
  6. threadFactory:线程工厂,用于创建线程,一般用默认的即可。
  7. handler:拒绝策略,当任务太多来不及处理,如何拒绝任务。

拒绝策略:
线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也
塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK 内置的拒绝策略如下:

  1. AbortPolicy : 直接抛出异常,阻止系统正常运行。

  2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的
    任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

  3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再
    次提交当前任务。

  4. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢
    失,这是最好的一种方案。
    以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际
    需要,完全可以自己扩展 RejectedExecutionHandler 接口。
    线程池工作过程:

  5. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面
    有任务,线程池也不会马上执行它们。

  6. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
    a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
    c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要
    创建非核心线程立刻运行这个任务;
    d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池
    会抛出异常 RejectExecutionException。

  7. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  8. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运
    行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它
    最终会收缩到 corePoolSize 的大小。
    java阻塞队列原理
    阻塞队列,关键字是阻塞,先理解阻塞的含义,在阻塞队列中,线程阻塞有这样的两种情况:

  9. 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。

  10. 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

    阻塞队列常用方法:

代码

execute方法在将来某个时间执行任务

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}

purge()试图从工作队列移除所有已取消的future任务。

public void purge() {final BlockingQueue<Runnable> q = workQueue;try {Iterator<Runnable> it = q.iterator();while (it.hasNext()) {Runnable r = it.next();if (r instanceof Future<?> && ((Future<?>)r).isCancelled())it.remove();}} catch (ConcurrentModificationException fallThrough) {// Take slow path if we encounter interference during traversal.// Make copy for traversal and call remove for cancelled entries.// The slow path is more likely to be O(N*N).for (Object r : q.toArray())if (r instanceof Future<?> && ((Future<?>)r).isCancelled())q.remove(r);}tryTerminate(); // In case SHUTDOWN and now empty}

getPoolSize()返回线程池中当前的线程

public int getPoolSize() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// Remove rare and surprising possibility of// isTerminated() && getPoolSize() > 0return runStateAtLeast(ctl.get(), TIDYING) ? 0: workers.size();} finally {mainLock.unlock();}}

自定义线程池功能简单

package cn.thread.线程;import java.util.LinkedList;public class ThreePool extends ThreadGroup {private boolean isClose = false;private LinkedList taskQueue;private static int threadPool_ID = 1;//线程池的构造方法public ThreePool(int poolSize) {super(threadPool_ID+"");//规定线程池的名称setDaemon(true);//守护线程taskQueue = new LinkedList();//创建队列for (int i=0;i<poolSize;i++){//启动线程任务new TaskThread(i).start();}}//添加新任务,并且执行public synchronized void executeTask(Runnable task){if(isClose){throw  new IllegalArgumentException("");}if(task!=null){taskQueue.add(task);//向任务中队列中添加一个任务notify();//唤醒}}private synchronized Runnable getTask(int id)  {try{while (taskQueue.size()==0){if(isClose){return null;}else{System.out.println("工作线程"+id+"在等待任务!");wait();}}}catch (Exception e){System.out.println("等待任务出现错误!");}System.out.println("工作线程"+id+"开始执行工作");return  (Runnable) taskQueue.removeFirst();}private class TaskThread extends Thread{private int id;public TaskThread(int id){super(ThreePool.this, String.valueOf(id));this.id = id;}@Overridepublic void run() {//判断当前线程是否处于中断状态while(!isInterrupted()){Runnable task =getTask(id); //通过id获取队列中的线程对象if(task==null){return;}try{task.run();}catch (Exception e){e.printStackTrace();}}}}//关闭线程池public synchronized  void closeThreadPool() throws InterruptedException {if(!isClose){//判断标识//等待线程的任务执行完毕waitTaskFinish();isClose = true;taskQueue.clear();interrupt();}}public void waitTaskFinish() throws InterruptedException {synchronized (this){isClose  = true;notifyAll();}Thread[] threads = new Thread[activeCount()];//线程组中的活动数量相同int count = enumerate(threads);//循环等待线程结束for(int i=0;i<count;i++){threads[i].join();}}}

只需这篇文章java线程池原理便懂了!♥♥相关推荐

  1. Java 线程池原理总结

    Java 线程池原理总结 (一)什么是线程池 线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它 ...

  2. JAVA线程池原理以及几种线程池类型介绍

    在什么情况下使用线程池? 1.单个任务处理的时间比较短      2.将需处理的任务的数量大 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销      2.如不使用线程池, ...

  3. Java 线程池原理及四种常用的线程池使用

    推荐阅读:Java线程池实现原理及其在美团业务中的实践 文章目录 什么是线程池 使用线程池的好处 线程池的实现原理 流程图分析 源码分析 线程池的使用 向线程池中提交任务 newCachedThrea ...

  4. Java线程池原理与实例详解

    Wiki 采用new Thread的方式产生多线程,可能有以下一些问题:  线程的创建和销毁开销很大,尤其是有些线程的存在时间较短:  线程的创建和销毁过程中伴随着CPU在线程间的切换,开销很大: ...

  5. JAVA线程池原理-1

    文章目录 参考文章 为什么使用线程池 线程池控制变量 参考文章 线程池原理 Java并发之线程池ThreadPoolExecutor源码分析学习 为什么使用线程池 javadoc中的原文 Thread ...

  6. Java 线程池原理和队列详解

    转载请标明出处:http://blog.csdn.net/xx326664162/article/details/51701508 文章出自:薛瑄的博客 你也可以查看我的其他同类文章,也会让你有一定的 ...

  7. java 线程池原理分析

    一.为什么使用线程池 1.降低资源消耗,减少线程创建和销毁次数,每个工作线程可以重复利用,执行多个任务 2.可根据系统承受能力,调整工作线程的数目,防止消耗过多的内存 二.java 线程池使用 Exe ...

  8. (转载) Java线程池原理

    Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后 ...

  9. 理解(计算、网络,存储)虚拟化,只需一篇文章

    计算虚拟化 1.什么是 虚拟化 什么是虚拟化:虚拟化是一种技术,将物理设备进行逻辑化,转化成文件夹或文件,实现软硬件解耦 好处: 提高资源利用率 软硬件解耦(具备其他高级特性:HA,DRS,DPM) ...

最新文章

  1. display vs visibility
  2. 线性代数的学习及相关资源
  3. oracle 中文查询不到,使用PLSQL查询Oracle语句中文无法匹配问题解决
  4. boost::system::system_error相关的测试程序
  5. MyBatis 插件原理与自定义插件-插件编写与注册
  6. ubuntu cd 改变路径
  7. python做词典_Python实现创建字典
  8. Visual Studio解决方案的目录结构设置和管理
  9. 在Jupyter Notebook中运行R (附一系列疑难解答)
  10. 商务与经济统计++原书第12版+[(美)安德森著][机械工业出版社][2015.07][515页][13854037]第一章读书笔记
  11. 2008 SCI 影响因子(Impact Factor)
  12. Linux进程调度策略
  13. STM32 W5500 MQTT Client 发布订阅及断线重连
  14. 树莓派外挂ESP32-USB
  15. Linux入门(14)——系统启动修复
  16. [图像处理-1]:颜色中英文对照表 颜色名字 色彩名称
  17. 有你智居告诉你,智能门禁是如何做到出入小区只需1秒钟的?
  18. python求x的y次方logn_[转载]关于开14次方的计算方法
  19. 实例教程_次世代兽人制作教程
  20. 这几个网站的使用技巧,值得反复读,反复练~

热门文章

  1. 手机丢了不可怕,手机卡丢了才最可怕!
  2. 两步实现安卓手机秒变网络摄像头
  3. 前端导出pdf以及导出内容截断的解决办法
  4. 傅连仲主编的《计算机应用基础》,学生教学论文,关于计算机应用基础课程标准研读的必要性相关参考文献资料-免费论文范文...
  5. swagger转换成word文档
  6. GroovyGrails
  7. 教育界杂志教育界杂志社教育界编辑部2022年第24期目录
  8. python循环结构教学设计_Python程序设计 循环结构说课稿
  9. 性能测试监控指标和分析
  10. asciidoc_如何使用AsciiDoc创建博客