Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >多线程基础(一): 线程概念及生命周期

多线程基础(一): 线程概念及生命周期

作者头像
冬天里的懒猫
发布于 2020-09-08 13:13:34
发布于 2020-09-08 13:13:34
79600
代码可运行
举报
运行总次数:0
代码可运行

终于完成了前面关于Map容器的源码阅读部分,并进行了相关的总结,从现在开始,进入多线程基础的学习。先来看看多线程的基本概念.

1.基本概念

在日常工作中,除了线程,还有进程、协程等,现在来看看这些基本概念。

1.1 进程

什么是进程,相信大家都知道什么是进程却很难解释清楚。百科中的解释是:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 实际上,可以理解为,进程是操作系统中的某个程序关于某个数据集合的一次运行活动。是操作系统动态执行的基本单元。操作系统以进程为基本单元进行资源分配和任务执行。

正如任务管理器中所看到的,所有程序都是通过进程来运行的。进程是计算机分配资源和执行任务的基本单元。只有进程启动了,程序才能正常运行,否则,程序就是静态的文件,不会工作。是进程将程序有静态文件变成了动态的执行过程。 进程在执行的过程中,互相之间的堆和栈的内存空间都是独立的,不能共享。只能通过共享存储、信号量、消息队列等相关方式进行通信。 在linux系统中,所有进程信息都采用task_struct结构体进行描述。每个进程都有一个pid编号,另外我们需要知道的是linux进程的状态。

  • TASK_RUNNING (可运行状态):处于这种状态的进程,要么正在运行、要么正准备运行。正在运行的进程就是当前进程(由current所指向的进程),而准备运行的进程只要得到CPU就可以立即投入运行,CPU是这些进程唯一等待的系统资源。
  • TASK_INTERRUPTIBLE(可中断的等待状态):表示进程被阻塞(睡眠),直到某个条件达成,进程的状态就被设置为TASK_RUNNING。处于该状态的进程正在等待某个事件(event)或某个资源,而被挂起。对应的task_struct结构被放入对应事件的等待队列中。处于可中断等待态的进程可以被信号(外部中断触发或者其他进程触发)唤醒,如果收到信号,该进程就从等待状态进入可运行状态,并且加入到运行队列中,等待被调度。
  • TASK_UNINTERRUPTIBLE(不可中断的等待状态):该状态与 TASK_INTERRUPTIBLE 状态类似,也表示进程被阻塞,处于睡眠状态。当进程等待的某些条件被满足了之后,内核也会将该进程的状态设置为 TASK_RUNNING。但是,处于这个状态下的进程不能在接收到某个信号之后立即被唤醒。这时该状态与 TASK_INTERRUPTIBLE 状态唯一的区别。
  • __TASK_STOPPED(暂停状态):此时的进程暂时停止运行来接受某种特殊处理。通常当进程接收到SIGSTOP、SIGTSTP、SIGTTIN或 SIGTTOU信号后就处于这种状态。例如,正接受调试的进程就处于这种状态。
  • __TASK_TRACED(跟踪状态):当前进程正在被另一个进程所监视。
  • EXIT_ZOMBIE(僵死状态):进程虽然已经终止,但由于某种原因,父进程还没有执行wait()系统调用,终止进程的信息也还没有回收。顾名思义,处于该状态的进程就是死进程,这种进程实际上是系统中的垃圾,必须进行相应处理以释放其占用的资源。
  • EXIT_DEAD:一个进程的最终状态。 linux的各进程状态和内核调用如下图所示:

1.2 线程

线程才是本文需要讨论的重点。最开始的操作系统,只有进程的概念,并没有线程。但是随着计算机的发展,对CPU的要求越来越高,多进程模式进行同步的开销非常大,进程间切换是很消耗资源的。因此,就发明了线程,在进程内部,在抽象得更加细化的线程概念,一个进程可以有多个线程。这样一来,同一个进程的线程之间,除了栈是私有的之外,堆区的内存就能共享。这样进程就由其内存空间和一个或者多个线程组成。 实际上写过代码的人都知道,线程这个概念是一个功能抽象的过程。之前只能用一个进程来执行,现在分为多个线程之后,由于CPU时间片的关系,可以认为多个线程能同时工作。 我们来对比一下进程和线程:

  • 线程是程序执行的最小单位,而进程是操作系统资源分配的最小单位。
  • 一个进程由一个或者多个线程组成,线程是一个进程中代码的不同执行路线。
  • 进程间相互独立,进程内部的线程间共享堆内存以及一些进程级的资源。各进程内的线程互相不可见。
  • 线程间切换虽然也耗资源,但是相对进程切换要快得多。

进程与线程的关系如下:

多线程抽象之后的时间片分配:

实际上通过时间片之后,由于时间片很短,因此用户在使用上就感觉多线程能同时工作。 线程的生命周期我们将在后面单独来说明。

1.3 协程

协程,又称微线程,纤程。英文名Coroutine。协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。协程实际上是在线程概念上再次进行的抽象。我们回顾线程的概念,通过线程解决了进程切换资源开销的问题。这样很多功能就可以通过在一个进程中由多个线程来实现。但是随着计算机系统的发展,计算机的性能虽然越来越好,但是赶不上需求发展的速度,虽然多线程也能解决高并发问题,但是不可忽视的是线程是一个操作系统底层的概念。一方面,线程切换需要开销,另外一方面,线程是一个底层概念,实现线程同步的话需要非常复杂的代码,来实现不同的锁机制,确保线程安全等问题。因此有没有一种不需要关心线程的切换,只需要固定的线程数,而是将任务拆分,类似于CPU时间片一样,如果我们可以将我们设计的程序,也可以类似于CPU时间片那样拆分为一个个细小的单元,之后我们就不用关心线程的切换,只需要将这些通用的任务进行切换即可。这样就会更加简单和提升效率。协程就应运而生。 我们再回想一下,如果再多线程的情况下编程,任务是不可拆分的,那么势必造成某些线程长期占有CPU执行,而某些线程由于执行的速度特别快,执行完成从之后就会在线程池中处于空闲状态。由于操作系统底层实际上只知道线程的运行状态,有多个线程,那么就必须维护多个。如果有线程偷懒的话,实际上是不经济的。这就好比在一个工厂中,由于任务拆分得不细,那么只有那么几个工人是忙碌状态。我们再来看看现代工业的流水线,任务分解到一个个的动作,在流水线上操作。这样工人的时间都能尽量用起来。对应到线程也是一样。只要线程存在,就会分配时间片,入果这个线程没有工作,那么就会资源浪费。现在我们可以将任务拆分得更细,这样一来,线程就不会空闲,CPU的利用率就上升了。 这样一来,首先会大大提升执行效率,不会有线程的切换开销。其次,就是不再需要考虑锁机制,在用户层面,只需要考虑如何将任务分解即可。

2 线程生命周期

在jvm中,jvm中的线程实际上是和操作系统的线程一一对应的。我们每当new一个thread,实际上就是在操作系统中真正的创建一个Thread。本文暂不涉及linux底层的线程模型。实际上线程在linux中就是以轻量进程的形式来运行的。线程的生命周期基本上等同于进程的生命周期。

2.1 传统的线程模型

在传统的线程模型中,可分为三态或者五态模型。 如下即是一个五态的模型:

  • 1.创建/初始化状态:指的是线程已经被创建完毕,但是还不能执行,等价于我们new了一个线程对象。实际上在操作系统层面,线程并没有真的创建。只是在各编程语言中完成了初始化状态。这是各编程语言所特有。
  • 2.就绪状态:也就是可运行状态。在这种状态下,操作系统底层已经将线程创建成功了,可以分配CPU进行执行。
  • 3.运行状态:当有空闲的CPU的时候,操作系统将CPU分配给一个可以运行的线程执行。这个被分配到CPU的线程的状态就会被修改为运行状态。
  • 4.阻塞状态:运行状态的线程如果调用一个阻塞的操作,或者对某个事件进行等待,则转换为阻塞状态,同时释放出CPU的使用权,处于阻塞状态的线程永远没有机会获得CPU,只有当阻塞事件结束或者等待的事件完成,才会从阻塞状态转变为就绪状态。等待下一次CPU事件片的分配。
  • 5.终止状态:线程执行完成或者出现异常就会进入终止状态,线程终止之后,线程的生命周期也就结束了,这个过程是不可逆的,内存资源将被回收。

这是传统的线程五态模型,在不同的语言中,可能会被简化或者细化。java就对部分状态进行了简化和细化。我们来看看JAVA中的线程模型。

2.2 java中的线程模型

在java中,线程的生命周期分为:

  • 1.NEW 即初始化状态
  • 2.RUNNALBLE 可运行/运行状态
  • 3.BLOCKED 阻塞状态
  • 4.WAITING 无时限等待状态
  • 5.TIMED_WAITING 有时限等待状态
  • TERMINATED 终止状态

实际上BLOCK、WAITING、TIMED_WAITING三种状态都是前面五态模型中的阻塞状态的细化。这三种状态的任意一种状态都不能获得CPU的执行权。而RUNNABLE则是将五态模型中的就绪和运行状态进行了合并。对应起来如下:

实际上这个图可以更细:

这就是java线程中各状态转换的情况。

2.2.1 从NEW到RUNNABLE状态

java刚创建的线程是NEW状态,在java中,实现线程的方法有两种,一种是继承Thread类,一种是实现Runnable接口。

2.2.1.1 继承Thread类

代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyThread extend Thread{
    public void run(){
      //详细内容    
    }
    
}

MyThread t = new MyThread();
2.2.1.2 实现Runnable接口

另外一种方法是实现Runnable接口。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class MyThread implements Runnable {
    @Override
    public void run() {
        //详细内容
    }
}
Thread t = new Thread(new MyThread());

以上就是在java中启动线程的两种方法。 之后只要执行start方法,就完成了从NEW到RUNNABLE状态的转换。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MyThread t = new MyThread();
t.start();
2.2.2 RUNNABLE与WAITING状态转换

在RUNNABLE到WAITING状态装转换的过程中,有如下情况:

  • 在synchronized中调用Object.wait()方法。
  • 调用Thread.join()方法,join()是一种同步方法,在某线程中执行另外一个线程A的 A.join(),就会导致当前线程进入WAITING状态,之后A执行完之后,这个线程才才从WAITING转换为RUNNABLE。
  • 调用LockSupport.park()方法,这个我们后续详细分析LockSupport的时候介绍。这个方法也能将线程从RUNNABLE变成WAITING状态。之后通过LockSupport.unpark(Thread t)则可以将线程从WAITTING变成RUNNABLE状态。
2.2.3 RUNNABLE与TIME_WAITING状态转换

从RUNNABLE到TIME_WAITING的转换主要有如下情况:

  • 1.Thread.sleep(long millis)方法。
  • 2.在获取synchronized之后 Object.wait(long timeout)方法。
  • 3.Thread,join(long timeout)方法。
  • 4.调用LockSupport.parkNanos(Object blocker,long deadline)方法。
  • 5.LockSupport.parkUntil(long deadline)方法。 从TIME_WAITING到RUNNABLE的方法与从WAITING到RUNNABLE一样。
  • 1.Object.notify()
  • 2.Object.notifyAll()
  • 3.LockSupport.unpark(Thread t) 可以发现,实际上TIME_WAITING与WAITING状态的区别在于调用的方法中多了时间参数。
2.2.4 RUNNING与BLOCKED的转换

从RUNNIG状态到BLOCKED的过程只有一种情况,那就是使用synchronized的时候,synchronized修饰的方法或者同步块,同时只允许一个线程执行,其他线程就会等待,这种情况下就会从RUNNBING转换到BLOCKED状态。当等待的线程获得了锁之后,就会从BLOCKED状态转变为RUNNING状态。

2.2.5 从RUNNING到TERMINATED状态

线程执行完成之后,会自动转换到TERMINATED状态。另外在run的过程中如果遇到异常,也会停止,线程状态会变为TERMINATED状态。如果run的过程很慢需要终止,我们需要使用interrupt()方法。 需要注意的是 stop()方法已经标注为@Deprecated,不再建议使用。 关于stop方法和interrupt方法的区别,我们在后续讨论Thread源码的时候详细来分析。

3.总结

本文重点是对线程的定义及其生命周期进行了分析,同时也了解了进程和协程。需要注意的是五态模型,这实际上是一个抽象的概念。实际上在linux底层实现线程的时候,细节上会有很多的不同。我们需要掌握java线程的状态模型以及各状态之间的转换。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/09/05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
(二)Java线程与系统线程,生命周期
②Runnable 调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU;第二种情况是线程在处于RUNNING状态时并没有运行完自己的run方法,时间片用完之后回到RUNNABLE状态;还有种情况就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。
HaC
2020/12/30
4930
(二)Java线程与系统线程,生命周期
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
在Thread类中有内部类 枚举State,用于抽象描述Java线程的状态,共有6种不同的状态
noteless
2019/03/04
9020
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
Java线程状态(生命周期)以及线程状态转换详解
线程状态转换进入等待/超时等待进入等待状态进入超时等待LockSupport类简介过期的suspend和resume方法
用户7886150
2021/04/20
7140
Java线程生命周期与状态切换
最近有点懒散,没什么比较有深度的产出。刚好想重新研读一下JUC线程池的源码实现,在此之前先深入了解一下Java中的线程实现,包括线程的生命周期、状态切换以及线程的上下文切换等等。编写本文的时候,使用的JDK版本是11。
Throwable
2020/06/23
8780
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
在前面的几篇博客里,我们学习了Java的多线程,包括线程的作用、创建方式、重要性等,那么今天我们就要正式踏入线程,去学习更加深层次的知识点了。
JavaBuild
2024/05/27
1060
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
实现线程中断的操作:设置一个标记位,表示是否被中断,线程在执行时循环判断是否被中断
终有救赎
2023/10/16
2010
Java多线程基础(线程与进程的区别,线程的创建方式及常用api,线程的状态)
浅谈Java多线程基础及其使用方式
本讲主要介绍多线程,多线程编程是Java编程中的一个重要部分。它允许程序同时执行多个任务,这有助于提高程序的效率和性能。在Java中,可以通过实现Runnable接口或继承Thread类来创建线程。
小明爱吃火锅
2023/09/20
3402
多线程基础
<font color="red">中央处理器</font>: 作为计算机系统的运算和控制的核心,是信息处理、程序运行的最终执行单元。
程序员NEO
2023/09/30
2500
多线程基础
Java的线程
每个线程有自己的程序计数器、栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其他线程共享文件描述符、虚拟地址空间等。
真正的飞鱼
2023/05/14
2670
Java的线程
面试必答题“聊聊Java中线程的生命周期状态”如何破?
👆点击“博文视点Broadview”,获取更多书讯 “聊聊Java中线程的生命周期状态吧!” 这几乎是一道面试必答题,这道题怎么答才是最佳答案呢?本文就带大家来破解一下! 01 一张图说明线程生命周期 JVM源码中将线程的生命周期分为新建(New)、可运行(Runnable)、阻塞(Blocked)、等待(Waiting)、超时等待(Timed_Waiting)和终止(Terminated)这6种状态。 在系统运行过程中不断有新的线程被创建,老的线程在执行完毕后被清理,线程在排队获取共享资源或者锁时将被
博文视点Broadview
2022/07/04
3250
面试必答题“聊聊Java中线程的生命周期状态”如何破?
线程与线程池的那些事之线程篇
线程,线程池,单线程,多线程,线程池的好处,线程回收,创建方式,核心参数,底层机制,拒绝策略,参数设置,动态监控,线程隔离
秦怀杂货店
2021/05/20
4030
【高并发】线程的生命周期其实没有我们想象的那么简单!!
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:
冰河
2020/10/29
3500
【高并发】线程的生命周期其实没有我们想象的那么简单!!
话说 线程的概念&生命周期
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
木子的昼夜
2021/04/05
3530
话说  线程的概念&生命周期
线程是什么?多线程?
线程在面试中已经是常客了,也是我们必备的知识点,关于线程,问的最多的便是线程是什么?为什么使用多线程?多线程的示例以及解决方案?线程池是什么? 一.线程是什么? java.lang.Thread类中有
Twcat_tree
2022/11/30
4380
线程是什么?多线程?
说说线程的生命周期和状态
Java 线程在运行的生命周期中的指定时刻,只可能处于下面 6 种不同状态的其中一个(图源《Java 并发编程艺术》4.1.4 节)。
happyJared
2019/07/03
1.2K0
说说线程的生命周期和状态
图解 Java 线程生命周期
创建线程(NEW),然后线程做自己的工作(RUNNABLE),做完之后就终止了(TERMINATED)。
dys
2020/08/20
3.4K0
图解 Java 线程生命周期
Java线程状态
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态 英文翻译过来是线程还是没有开始执行。 首先,既然已经有状态了,那肯定是已经创建好线程对象了(如果对象都没有,何来状态这一说?),这样一来问题的焦点就在于还没有开始执行,我们都知道当调用线程的start()方法时,线程不一定会马上执行,因为Java线程是映射到操作系统的线程进行执行,此时可能还需要等操作系统调度,但此时该线程的状态已经为RUNNABLE了
JavaEdge
2018/04/28
1.8K0
Java线程状态
Java并发编程(01):线程的创建方式,状态周期管理
进程是计算机中的程序,关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
知了一笑
2020/03/11
5410
Java并发编程之基础
使用FutureTask可以用泛型指定线程的返回值类型(Runnable的run方法没有返回值)
Java微观世界
2025/01/21
890
Java并发编程之基础
相关推荐
(二)Java线程与系统线程,生命周期
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验