百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

这篇文章带你彻底理解线程_何谓线程

wptr33 2025-02-19 14:10 13 浏览

1 线程的意义

操作系统支持多个应用程序同时执行,每个应用至少对应一个 进程 ,彼此之间的操作和数据不受干扰。当一个进程需要磁盘IO的时候,CPU就切换到另外的进程,提高了CPU利用率。

有了进程,为什么还要线程?因为进程的成本太高了。

启动新的进程必须分配独立的内存空间,建立数据表维护它的代码段、堆栈段和数据段,这是昂贵的多任务工作方式。如果两个进程之间需要通信,要采用管道通信、消息队列、共享内存等等方式。线程可以看作轻量化的进程,或者粒度更小的进程。线程之间使用相同的地址空间,切换线程的时间远远小于切换进程的时间。一个进程的开销大约是线程开销的30倍左右。

随着操作系统的发展,进程已经演变成了线程容器的角色。进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程,同一进程的所有线程共享该进程的所有资源。

2 详解Java线程

我们以Java语言和JVM为例,了解一下线程的实现原理。

2.1 线程的底层实现

启动一个Java程序会创建一个JVM进程,JVM创建、管理线程本质都是调用操作系统接口。

public class TestThreadStart {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("start thread now ");
        }, "TestThreadStart");
        thread.run();
        System.out.println("the state of thread is " + thread.getState().name());
        thread.start();
        System.out.println("the state of thread is " + thread.getState().name());
    }
}

以上代码演示了使用start方法启动线程,run方法只是执行同步方法,输出结果如下:

start thread now 
the state of thread is NEW
the state of thread is RUNNABLE
start thread now

JVM源码文件
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Thread.java 中,可以看到线程启动的start方法调用本地方法start0。

public void start() {
        synchronized (this) {
            // zero status corresponds to state "NEW".
            if (holder.threadStatus != 0)
                throw new IllegalThreadStateException();
            start0();
        }
    }
    
    private native void start0();

源码文件
https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libjava/Thread.c 中,实现了start0方法映射到JVM本地方法。

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive0",         "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield0",           "()V",        (void *)&JVM_Yield},
    {"sleep0",           "(J)V",       (void *)&JVM_Sleep},
    {"currentCarrierThread", "()" THD, (void *)&JVM_CurrentCarrierThread},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"setCurrentThread", "(" THD ")V", (void *)&JVM_SetCurrentThread},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",       "()[" THD,    (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"getStackTrace0",   "()" OBJ,     (void *)&JVM_GetStackTrace},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
    {"extentLocalCache",  "()[" OBJ,    (void *)&JVM_ExtentLocalCache},
    {"setExtentLocalCache", "([" OBJ ")V",(void *)&JVM_SetExtentLocalCache},
    {"getNextThreadIdOffset", "()J",     (void *)&JVM_GetNextThreadIdOffset}
};

在源码文件
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/thread.cpp 可以看到启动线程依赖系统级方法
os::start_thread(thread)

void Thread::start(Thread* thread) {
  // Start is different from resume in that its safety is guaranteed by context or
  // being called from a Java method synchronized on the Thread object.
  if (thread->is_Java_thread()) {
    // Initialize the thread state to RUNNABLE before starting this thread.
    // Can not set it after the thread started because we do not know the
    // exact thread state at that time. It could be in MONITOR_WAIT or
    // in SLEEPING or some other state.
    java_lang_Thread::set_thread_status(JavaThread::cast(thread)->threadObj(),
                                        JavaThreadStatus::RUNNABLE);
  }
  os::start_thread(thread);
}

在源码文件
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/os.cpp 找到
os::start_thread 方法,可以看到系统创建了线程,并且状态设置为RUNNABLE。

void os::start_thread(Thread* thread) {
  OSThread* osthread = thread->osthread();
  osthread->set_state(RUNNABLE);
  pd_start_thread(thread);
}

Linux系统并没有把线程和进程区别对待,无论线程还是进程都是一个数据结构,用 task_struct 结构体表示,唯一的区别是共享的数据区域不同。

struct task_struct {
    // 进程状态
    long              state;
    // 虚拟内存结构体
    struct mm_struct  *mm;
    // 唯一进程号
    pid_t             pid;
    // 指向父进程的指针
    struct task_struct   *parent;
    // 子进程列表
    struct list_head      children;
    // 存放文件系统信息的指针
    struct fs_struct      *fs;
    // 进程/线程打开的文件指针
    struct files_struct   *files;
};

以上代码是 task_struct 的极少部分字段。mm_struct是进程的虚拟内存空间,files_struct是进程将要读写的文件。Linux系统将一切外设和磁盘文件都当做文件处理,files_struct代表所有的IO操作。

从上图可以看到,Linux创建进程和子进程会申请不同的内存空间,读写不同的文件;创建进程和进程下的线程,共享了内存空间,读写一样的文件。因此多线程应用程序要利用锁机制,避免在同一区域写入错乱数据的问题。

2.2 线程的生命周期

操作系统的线程生命周期可以分为五种状态。分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。JVM将线程等待状态细分成两种,一共六种状态。

  • NEW:创建。
  • RUNNABLE:运行中。
  • BLOCKED:受阻塞并等待某个监视器锁。
  • WAITING:无限期地等待。
  • TIMED_WAITING:等待指定时间。
  • TERMINATED:终止。

2.3 线程的优先级

操作系统调度线程有两种方式:

  • 协作式调度:当前线程完全占用CPU时间,执行时间由线程本身控制,直到运行结束,系统才执行下一个线程。可能出现一个线程一直占有CPU,而其他线程等待,导致整个系统崩溃。
  • 抢占式调度:操作系统决定下一个占用CPU时间的是哪一个线程,定期的中断当前正在执行的线程,任何一个线程都不能独占。不会因为一个线程而影响整个进程的执行,但是频繁阻塞和调度会造成系统资源的浪费。

JVM的线程调度默认是抢占式调度,线程调度器按照优先级决定调度哪个线程来执行。线程优先级的范围是1~10,默认的优先级是5,10极最高。线程优先级高的不一定先执行,优先级低只是获得调度的概率低,并不是一定最后被调度。通过 setPriority() 可以改变线程优先级。

2.4 JVM守护线程

守护线程是一种JVM中特殊的线程,在后台完成一些系统性的服务,比如垃圾回收。应用程序创建的线程叫做用户线程,完成具体的业务操作。程序中所有的用户线程执行完毕之后,不管守护线程是否结束,JVM都会自动结束。任何线程都可以通过 setDaemon() 设置为守护线程和用户线程,如下代码所示:

public class DaemonThreadDemo {

    public static void main(String[] args) {
        System.out.println("--主线程开始--");
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("执行守护线程");
            }
        });
        thread.setDaemon(true);
        thread.start();
        System.out.println("--主线程结束--");
    }
}

程序运行结果:

--主线程开始--
--主线程结束--
执行守护线程
执行守护线程
执行守护线程
执行守护线程
执行守护线程
Process finished with exit code 0

当一个应用程序需要在后台持续做某件事情,就是守护线程的典型应用场景。比如开发一款社交软件,开启守护线程持续监听聊天消息。当应用程序退出时,守护线程一定会终止。

相关推荐

【推荐】一款开源免费、美观实用的后台管理系统模版

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍...

Android架构组件-App架构指南,你还不收藏嘛

本指南适用于那些已经拥有开发Android应用基础知识的开发人员,现在想了解能够开发出更加健壮、优质的应用程序架构。首先需要说明的是:AndroidArchitectureComponents翻...

高德地图经纬度坐标批量拾取(高德地图批量查询经纬度)

使用方法在桌面上新建一个index.txt文件,把下面的代码复制进去保存,再把文件名改成index.html保存,双击运行打开即可...

flutter系列之:UI layout简介(flutter ui设计)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。...

Android开发基础入门(一):UI与基础控件

Android基础入门前言:...

iOS的布局体系-流式布局MyFlowLayout

iOS布局体系的概览在我的CSDN博客中的几篇文章分别介绍MyLayout布局体系中的视图从一个方向依次排列的线性布局(MyLinearLayout)、视图层叠且停靠于父布局视图某个位置的框架布局(M...

TDesign企业级开源设计系统越发成熟稳定,支持 Vue3 / 小程序

TDesing发展越来越好了,出了好几套组件库,很成熟稳定了,新项目完全可以考虑使用。...

WinForm实现窗体自适应缩放(winform窗口缩放)

众所周知,...

winform项目——仿QQ即时通讯程序03:搭建登录界面

上两篇文章已经对CIM仿QQ即时通讯项目进行了需求分析和数据库设计。winform项目——仿QQ即时通讯程序01:原理及项目分析...

App自动化测试|原生app元素定位方法

元素定位方法介绍及应用Appium方法定位原生app元素...

61.C# TableLayoutPanel控件(c# tabcontrol)

摘要TableLayoutPanel在网格中排列内容,提供类似于HTML元素的功能。TableLayoutPanel控件允许你将控件放在网格布局中,而无需精确指定每个控件的位置。其单元格...

想要深入学习Android性能优化?看完这篇直接让你一步到位

...

12个python数据处理常用内置函数(python 的内置函数)

在python数据分析中,经常需要对字符串进行各种处理,例如拼接字符串、检索字符串等。下面我将对python中常用的内置字符串操作函数进行介绍。1.计算字符串的长度-len()函数str1='我爱py...

如何用Python程序将几十个PDF文件合并成一个PDF?其实只要这四步

假定你有一个很无聊的任务,需要将几十个PDF文件合并成一个PDF文件。每一个文件都有一个封面作为第一页,但你不希望合并后的文件中重复出现这些封面。即使有许多免费的程序可以合并PDF,很多也只是简单的将...

Python入门知识点总结,Python三大数据类型、数据结构、控制流

Python基础的重要性不言而喻,是每一个入门Python学习者所必备的知识点,作为Python入门,这部分知识点显得很庞杂,内容分支很多,大部分同学在刚刚学习时一头雾水。...