一文带你深入剖析Java线程池的前世今生

 更新时间:2022年10月18日 08:14:26   作者:一无是处的研究僧  
这篇文章主要带大家介绍了深入剖析一下Java线程池的前世今生,了解线程池的原理以及为什么需要线程池。文中的示例代码讲解详细,需要的可以参考一下

由线程到线程池

线程在做什么

灵魂拷问:写了那么多代码,你能够用一句话简练描述线程在干啥吗?

public class Demo01 {

  public static void main(String[] args) {
    var thread = new Thread(() -> {
      System.out.println("Hello world from a Java thread");
    });
    thread.start();
  }
}

我们上面的这个用线程输出字符串的代码来进行说明。我们知道上面的Java代码启动了一个线程,然后执行lambda函数,在以前没有lambda表达式的时候我们可以使用匿名内部类实现,向下面这样。

public class Demo01 {

  public static void main(String[] args) {
    var thread = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("Hello world from a Java thread");
      }
    });
    thread.start();
  }
}

但是本质上Java编译器在编译的时候都认为传递给他的是一个对象,然后执行对象的run方法。刚刚我们使用的Thread的构造函数如下:

    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }

Thread在拿到这个对象的时候,当我们执行Threadstart方法的时候,会执行到一个native方法start0

当JVM执行到这个方法的时候会调用操作系统给上层提供的API创建一个线程,然后这个线程会去解释执行我们之前给Thread对象传入的对象的run方法字节码,当run方法字节码执行完成之后,这个线程就会退出。

看到这里我们仔细思考一下线程在做一件什么样的事情,JVM给我们创建一个线程好像执行完一个函数(run)的字节码之后就退出了,线程的生命周期就结束了。确实是这样的,JVM给我们提供的线程就是去完成一个函数,然后退出(记住这一点,这一点很重要,为你后面理解线程池的原理有很大的帮助)。事实上JVM在使用操作系统给他提供的线程的时候也是给这个线程传递一个函数地址,然后让这个线程执行完这个函数。只不过JVM给操作系统传递的函数,这个函数的功能就是去解释执行字节码,当解释执行字节码完成之后,这个函数也会退出(被系统回收)。

看到这里可以将线程的功能总结成一句话:执行一个函数,当这个函数执行完成之后,线程就会退出,然后被回收,当然这个函数可以调用其他的函数,可能你会觉得这句话非常简单,但是这句话会我们理解线程池的原理非常有帮助。

为什么需要线程池

上面我们已经谈到了,当我们执行start的方法的时候,最终会走到start0方法,这是一个native方法,JVM在执行这个方法的时候会通过系统底层函数创建一个线程,然后去执行run方法,这里需要注意,创建线程是需要系统资源的,比如说内存,因为操作系统是系统资源的管理者,因此一般需要系统资源的方法都需要操作系统的参与,因此创建线程需要操作系统的帮忙,而一旦需要操作系统介入,执行代码的状态就需要从用户态到内核态转换(内核态能够执行许多用户态不能够执行的指令),当操作系统创建完线程之后有需要返回用户态,我们的代码将继续被执行,整个过程像下面这样。

从上图可以看到我们需要两次的上下文切换,同时还需要执行一些操作系统的函数,这个过程是非常耗时间的,如果在并发非常高的情况,我们频繁的去生成线程然后销毁,这对我们程序的性能影响还是非常大的。因此许许多多聪明的程序员就想能不能不去频繁的创建线程而且也能够完成我们的功能——我们创建线程的目的就是想让我们的程序完成的更加快速,让多个不同的线程同时执行不同的任务。于是线程池就被创造出来了。线程池的结构大致如下所示:

线程池实现原理

在前面我们已经提到了关于线程池和线程比较重要的两个点:

  • 线程就是执行一个函数。
  • 线程池当中的线程可以执行很多函数,但是不会退出。

凭借你朴素的情感,你觉得如何实现上面两个要求。答案就是在一个函数当中进行while循环,然后不断的从任务队列当中获取任务函数,然后进行执行,直到要求停止线程池当中的线程的时候线程再进行退出,整个过程的代码大致如下所示:

  public void run() {
    while (!isStopped) {
      try {
        Runnable task = tasks.take();
        task.run();
      } catch (InterruptedException e) {
        // do nothing
      }
    }
  }

关于线程池要有一部分细节也很重要,比如说我们需要一个并发安全的阻塞队列,如何保证所有线程正常退出等等,我们在下篇文章当中进行实现,而且将仔细分析这里面的细节。

总结

在本篇文章当中主要给大家介绍了线程到线程池的演化过程,主要介绍线程池实现的基本原理,主要解读了线程池背后的基本原理,希望大家有所收获!

以上就是一文带你深入剖析Java线程池的前世今生的详细内容,更多关于Java线程池的资料请关注脚本之家其它相关文章!

相关文章

  • 使用Java和Redis实现高效的短信防轰炸方案

    使用Java和Redis实现高效的短信防轰炸方案

    在当今互联网应用中,短信验证码已成为身份验证的重要手段,然而,这也带来了"短信轰炸"的安全风险 - 恶意用户利用程序自动化发送大量短信请求,导致用户被骚扰和企业短信成本激增,本文将详细介绍如何使用Java和Redis实现高效的短信防轰炸解决方案,需要的朋友可以参考下
    2025-04-04
  • Spring注解 TX声明式事务实现过程解析

    Spring注解 TX声明式事务实现过程解析

    这篇文章主要介绍了Spring注解 - TX 声明式事务实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • SpringBoot的java -jar命令启动原理解读

    SpringBoot的java -jar命令启动原理解读

    这篇文章主要介绍了SpringBoot的java -jar命令启动原理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • Spring整合ehCache全过程

    Spring整合ehCache全过程

    这篇文章主要介绍了Spring整合ehCache全过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • 完美解决PermGen space异常的问题

    完美解决PermGen space异常的问题

    这篇文章主要介绍了完美解决PermGen space异常的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • java基础二叉搜索树图文详解

    java基础二叉搜索树图文详解

    二叉树是一种非常重要的数据结构,它同时具有数组和链表各自的特点,下面这篇文章主要给大家介绍了关于java基础二叉搜索树的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-03-03
  • Java中时间戳的获取和转换的示例分析

    Java中时间戳的获取和转换的示例分析

    这篇文章主要介绍了Java中时间戳的获取和转换的示例分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • java使用ZipInputStream实现读取和写入zip文件

    java使用ZipInputStream实现读取和写入zip文件

    zip文档可以以压缩格式存储一个或多个文件,本文主要为大家详细介绍了java如何使用ZipInputStream读取Zip文档与写入,需要的小伙伴可以参考下
    2023-11-11
  • java类加载器和类反射使用示例

    java类加载器和类反射使用示例

    这篇文章主要介绍了java类加载器和类反射使用示例,需要的朋友可以参考下
    2014-03-03
  • Java多线程之线程安全问题详情

    Java多线程之线程安全问题详情

    这篇文章主要介绍了Java多线程之线程安全问题详情,线程安全问题是指因多线程抢占式执行而导致程序出现bug的问题。内容介绍详细内容需要的小伙伴可以参考下面文章内容
    2022-06-06

最新评论