计算机是如何工作的,Java多线程编程

Posted 安陵容

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机是如何工作的,Java多线程编程相关的知识,希望对你有一定的参考价值。

一、冯诺依曼体系

现代的计算机,大多遵守 冯诺依曼体系结构 (Von Neumann Architecture)


CPU 中央处理器: 进行算术运算和逻辑判断.

AMD Ryzen 7 580OU with Radeon Graphics
GHz 叫做 CPU 的主频
这个数字越大,CPU 就算的越快,表示 1s 执行 32 亿条指令

存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)
输入设备: 用户给计算机发号施令的设备.
输出设备:计算机个用户汇报结果的设备

针对存储空间

  • 硬盘 > 内存 >> CPU

针对数据访问速度

  • CPU >> 内存 > 硬盘

认识计算机的祖师爷 – 冯诺依曼

冯·诺依曼(John von Neumann,1903年12月28日-1957年2月8日), 美籍匈牙利数学家、计算机科学家、物理学家,是20世纪最重要的数学家之一。冯·诺依曼是布达佩斯大学数学博士,在现代计算机、博弈论、核武器和生化武器等领域内的科学全才之一,被后人称为 “现代计算机之父”, “博弈论之父”.


二、CPU 基本工作流程

1、逻辑门

1.1、电磁继电器

电子开关 —— 机械继电器 (Mechanical Relay):
电磁继电器:通过通电 / 不通电来切换开关状态,得到 1 或者 0 这样的数据


1.2、门电路 (Gate Circuit)

基于上述的 “电子开关” 就能构造出基本的门电路,可以实现 1 位(bit) 的基本逻辑运算
最基础的门电路,有三种:
非门:可以对一个 0/1 进行取反. 0-> 1
与门:可以针对两个 0/1 进行与运算. 1 0 -> 0
或门:可以针对两个 0/1 进行或运算. 1 0 -> 1
针对二进制数据来进行的.不是"逻辑与”,此处是按位与

借助上述的基础门电路,能构造出一个更复杂的门电路:异或门
相同为0,相异为1。1 0 -> 1


1.3、运算器

基于上述的门电路,还可以搭建出一些运算器

半加器:是针对两个比特位,进行加法运算


全加器:是针对三个比特位,进行加法运算


1.4、加法器

基于上述的半加器和全加器,就可以构造出一个针对多个 bit 位的数据进行加法运算的加法器了


A和B是两个8 bit的数字
A0这个数字的第0位(最右)A1
A2
A3

电子开关=>基础的门电路=>异或门电路=>半加器=>全加器=>8位加法器

有了加法器之后,就可以计算不只是加法,还能计算减法、乘法、除法都是通过这个加法器来进行


2、寄存器,控制单元(CU)

CPU里面除了运算器之外,还有控制单元和寄存器(Register)

门电路 (电子开关)
CPU芯片来说,上面就集成了非常非常多的这些电子开关,一个CPU上面的电子开关越多,就认为是计算能力就越强

CPU里面除了运算器之外,还有控制单元寄存器

  • 寄存器是CPU内部用来存储数据的组件
    访问速度:寄存器是内存的3-4个数量级
    存储空间:比内存小很多很多,现在的x64的cpu (64位的cpu),大概有几十个寄存器,每个寄存器是8个字节,几百个字节,
    成本:CPU上面的这个寄存器,还是非常贵
    持久化:掉电之后数据丢失

  • 控制单元 CU(Control Unit)
    协调CPU来去进行工作
    控制单元最主要的工作,能够去执行指令
    后面进行详细的论述


3、指令(Instruction)

指令和编程密切相关。

编程语言,大概分成三类:

1、机器语言
通过二进制的数字,来表示的不同的操作
不同的CPU (哪怕是同一个厂商,但是不同型号的CPU),所支持的机器语言(指令)也可能存在差别
2、汇编语言
一个CPU到底支持哪些指令,生产厂商,会提供一个**"芯片手册”** 详细介绍CPU都支持哪些指令,每个指令都是干啥的
汇编语言和机器语言是一对一的关系 (完全等价)
不同的CPU支持的机器指令不一样,不同的CPU上面跑的汇编也不一样
学校的大部分的汇编语言都是针对一款上古神 U,Intel 8086 CPU
3、高级语言
(C,Java,JS)

指令是如何执行的?
自己构造出一个最简单的芯片手册:

假设CPU上有两个寄存器
A 00
B 01

0010 1010
这个操作的意思,就是把1010 内存地址上的数据给读取到A寄存器中
0001 1111
这个操作的意思,就是把 1111内存地址上的数据读到寄存器 B
0100 1000
这个操作的意思,就是把 A寄存器的值,存到 1000这个内存地址中
1000 0100
这个操作的意思,就是把 00寄存器和01寄存器的数值进行相加,结果放到 00 寄存器里

CPU的工作流程:(通过CU控制单元来实现的)

  1. 从内存中读取指令
  2. 解析指令
  3. 执行指令

咱们编写的程序,最终都会被编译器给翻译成 CPU 所能识别的机器语言指令,在运行程序的时候,操作系统把这样的可执行程序加载到内存中,cpu 就一条一条指令的去进行读取,解析,和执行,如果再搭配上条件跳转,此时,就能实现条件语句和循环语句


三、操作系统(Operating System)

操作系统是一组做计算机资源管理的软件的统称。目前常见的操作系统有:Windows系列、Unix系列、
Linux系列、OSX系列、android系列、ios系列、鸿蒙等

操作系统是一个搞 "管理的软件"

  1. 对下,要管理好各种硬件设备
  2. 对上,要给各种软件提供稳定的运行环境

1、进程/任务(Process/Task)

exe 可执行文件,都是静静的躺在硬盘上的,在你双击之前,这些文件不会对你的系统有任何影响
但是,一旦你双击执行这些 exe 文件,操作系统就会把这个 exe 给加载到内存中,并且让 CPU 开始执行exe内部的一些指令 (exe里面就存了很多这个程序对应的二进制指令)
这个时候,就已经把 exe给执行起来,开始进行了一些具体的工作
这些运行起来的可执行文件,称为 "进程"

这些都是机器上运行的进程:

  • 线程:
    线程是进程内部的一个部分进程包含线程,如果把进程想象成是一个工厂,那么线程就是工厂里的生产线,一个工厂里面可以有一个生产线或者也可以有多个生产线
    咱们写的代码,最终的目的都是要跑起来,最终都是要成为一些进程
    对于 java 代码来说,最终都是通过 java 进程来跑起来的 (此处的这个 java 进程就是咱们平时常说的jvm)
    进程 (process) 还有另一个名字任务 (task)

2、操作系统是如何管理进程的?

  1. 先描述一个进程 (明确出一个进程上面的一些相关属性)
  2. 再组织若干个进程 (使用一些数据结构,把很多描述进程的信息给放到一起,方便进行增删改查)

描述进程:操作系统里面主要都是通过 C/C++来实现的,此处的描述其实就是用的C语言中的 “结构体” (也就和Java的类差不多)
**操作系统中描述进程的这个结构体, "PCB" (process control block),进程控制块,这个东西不是硬件中的那个PCB板

组织进程:典型的实现,就是使用双向链表来把每个进程的PCB给串起来
操作系统的种类是很多的,内部的实现也是各有不同,咱们此处所讨论的情况,是以Linux这个系统为例,由于windows, mac 这样的系统,不是开源的,里面的情况我们并不知道


3、PCB中的一些属性:

1、pid (进程id)
进程的身份标识,进程的身份证号


2、内存指针
指明了这个进程要执行的代码 / 指令在内存的哪里,以及这个进程执行中依赖的数据都在哪里
当运行一个exe,此时操作系统就会把这个 exe 加载到内存中,变成进程
进程要执行的二进制指令 (通过编译器生成的), 除了指令之外还有一些重要的数据

3、文件描述符表:
程序运行过程中,经常要和文件打交道 (文件是在硬盘上的)
文件操作:打开文件,读/写文件,关闭文件
进程每次打开一个文件,就会在文件描述符表上多增加一项,(个文件描述符表就可以视为是一个数组,里面的每个元素,又是一个结构体,就对应一个文件的相关信息)

一个进程只要一启动,不管你代码中是否写了打开 / 操作文件的代码,都会默认的打开三个文件 (系统自动打开的),标准输入(System.in),准输出(System.out) 标准错误(System.err)
要想能让一个进程正常工作,就需要给这个进程分配一些系统资源:内存,硬盘,CPU

这个文件描述符表的下标,就称为文件描述符

4、进程调度:

  • 上面的属性是一些基础的属性,下面的一组属性,主要是为了能够实现进程调度
    进程调度:是理解进程管理的重要话题,现在的操作系统,一般都是 “多任务操作系统”(前身就是 “单任务操作系统”,同一时间只能运行一个进程),一个系统同一时间,执行了很多的任务

5、并行和并发:

  • 并行:微观上,两个CPU核心,同时执行两个任务的代码
  • 并发:微观上, 一个CPU核心,先执行一会任务1, 再执行一会任务,再执行一会任务…再执行一会任务
    只要切换的足够快, 宏观上看起来, 就好像这么多任务在同时执行一样

并行和并发这两件事, 只是在微观上有区分
宏观上咱们区分不了,微观上这里的区分都是操作系统自行调度的结果

例如6个核心,同时跑20个任务
这20个任务, 有些是并行的关系, 有些是并发的关系。可能任务A和任务B,一会是并行, 一会是并发….都是微观上操作系统在控制的,在宏观上感知不到
正因为在宏观上区分不了并行并发, 我们在写代码的时候也就不去具体区分这两个词实际上通常使用 “并发” 这个词, 来代指并行+并发
咱们只是在研究操作系统进程调度这个话题上的时候, 稍作区分但是其他场景上基本都是使用并发作为一个统称来代替的,并发编程


4、CPU 分配 —— 进程调度(Process Scheduling)

6、调度
所谓的调度就是 “时间管理”
并发就是规划时间表的过程,也就是“调度"的过程

7、状态
状态就描述了当前这个进程接下来应该怎么调度

  • 就绪状态:随时可以去 CPU 上执行
  • 阻塞状态 / 睡眠状态:暂时不可以去CPU上执行

Linux中的进程状态还有很多其他的…

8、优先级
先给谁分配时间,后给谁分配时间,以及给谁分的多,给谁分的少

9、记账信息
统计了每个进程,都分别被执行了多久,分别都执行了哪些指令,分别都排队等了多久了…
给进程调度提供指导依据的

10、上下文
就表示了上次进程被调度出 CPU 的时候,当时程序的执行状态。下次进程上CPU的时候,就可以恢复之前的状态,然后继续往下执行

进程被调度出CPU之前,要先把CPU中的所有的寄存器中的数据都给保存到内存中 (PCB的上下文字段中) ,相当于存档了
下次进程再被调度上CPU的时候,就可以从刚才的内存中恢复这些数据到寄存器中,相当于读档

存档+读档,存档存储的游戏信息,就称为 “上下文”


5、内存分配 —— 内存管理(Memory Manage)

进程的调度,其实就是操作系统在考虑CPU资源如何给各个进程分配
那内存资源又是如何分配的呢?

11、虚拟地址空间:
由于操作系统上,同时运行着很多个进程,如果某个进程,出现了bug 进程崩溃了,是否会影响到其他进程呢?
现代的操作系统 (windows, linux, mac… ) ,能够做到这一点,就是 “进程的独立性” 来保证的,就依仗了"虚拟地址空间"

例:如果某个居民核酸变成阳性了,是否会影响到其他的居民呢?
一旦发现有人阳性了,就需要立刻封楼封小区,否则就会导致其他人也被传染,

这个情况就类似于早期的操作系统,早期的操作系统,里面的进程都是访问同一个内存的地址空间。如果某个进程出现 bug,把某个内存的数据给写错了,就可能引起其他进程的崩溃

解决方案,就是把这个院子,给划分出很多的道路
这些道路之间彼此隔离开,每个人走各自的道理,这个时候就没事了,此时即使有人确诊,也影响不到别人了,

如果把进程按照虚拟地址空间的方式给划分出了很多份,这个时候不是每一份就只剩一点了嘛?? 虽然你的系统有百八十个进程,但是实际上从微观上看,同时执行的进程,就6个!!
每个进程能够捞着的内存还是挺多的,而且另一方面,也不是所有的进程都用那么多的内存,有的进程 (一个3A游戏,吃几个G),大多数的进程也就只占几M即可


6、进程间通信(Inter Process Communication)

12、进程间通信
进程之间现在通过虚拟地址空间,已经各自隔离开了,但是在实际工作中,进程之间有的时候还是需要相互交互的。

例:某业主A问:兄弟们,谁家有土豆,借我两个
业主B回答:我有土豆,我给你
设定一个公共空间,这个空间是任何居民都可以来访问的,
让B先把土豆放到公共空间中,进行消毒,再让A来把这个公共空间的土豆给取走,彼此就不容易发生传染

类似的,咱们的两个进程之间,也是隔离开的,也是不能直接交互的,操作系统也是提供了类似的 "公共空间”,
进程 A 就可以把数据见放到公共空间上,进程B再取走

进程间通信:
操作系统中,提供的 “公共空间” 有很多种,并且各有特点,有的存储空间大,有的小,有的速度快,有的慢.….
操作系统中提供了多种这样的进程间通信机制,(有些机制是属于历史遗留的,已经不适合于现代的程序开发)

现在最主要使用的进程间通信方式两种:
1.文件操作
2.网络操作 (socket)

总结:


四、多线程

1、线程(Thread)

为啥要有进程?因为我们的系统支持多任务了,程序猿也就需要 “并发编程”
通过多进程,是完全可以实现并发编程的,但是有点小问题:
如果需要频繁的创建而 / 销毁进程,这个事情成本是比较高的,如果需要频繁的调度进程,这个事情成本也是比较高的:
对于资源的申请和放,本身就是一个比较低效的操作,

创建进程就得分配资源:
1)内存
2)文件
销毁进程也得释放资源
1)内存
2)文件

如何解决这个问题?思路有两个:

  1. 进程池: (如数据库连接池,字符串常量池)
    进程池虽然能解决上述问题,提高效率。同时也有问题:池子里的闲置进程,不使用的时候也在消耗系统资源,消耗的系统资源太多了
  2. 使用线程来实现并发编程:
    线程比进程更轻量,每个进程可以执行一个任务,每个线程也能执行一个任务 (执行一段代码),也能够并发编程,
    创建线程的成本比创建进程要低很多。销毁线程,的成本也比销毁进程低很多。调度线程,的成本也比调度进程低很多。
    Linux 上也把线程称为轻量级进程(LWP light weight process)

2、为什么线程比进程更轻量?

  1. 进程重量是重在哪里:重在资源申请释放 (在仓库里找东西…)
  2. 线程是包含在进程中的一个进程中的多个线程,共用同一份资源 (同一份内存+文件)
    只是创建进程的第一个线程的时候 (由于要分配资源)成本是相对高的,后续这个进程中再创建其他线程,这个时候成本都是要更低一些,所以为什么更轻量?少了申请释放资源的过程

可以把进程比作一个工厂,假设这个工厂有一些生产任务,例如要生产 1w 部手机
要想提高生产效率:
1). 搞两个工厂,一个生产 5k (多创建了一个进程)
2). 还是一个工厂,在一个工厂里多加一个生产线,两个生产线并行生产,一个生产线生产5k,(多创建了一个线程)
最终生产1w个手机,花的时间差不多,但是这里的成本就不一样了

多加一些线程,是不是效率就会进一步提高呢?一般来说是会,但是也不一定
如果线程多了,这些线程可能要竞争同一个资源,这个时候,整体的速度就收到了限制,整体硬件资源是有限的

总结进程与线程的区别:

  1. 进程包含线程,一个进程里可以有一个线程,也可以有多个线程
  2. 进程和线程都是为了处理 并发编程 这样的场景
    但是进程有问题,频繁创建和释放的时候效率低,相比之下,线程更轻量,创建和释放效率更高。为啥更轻量?少了申请释放资源的过程
  3. 操作系统创建进程,要给进程分配资源,进程是操作系统分配资源的基本单位
    操作系统创建的线程,是要在 CPU上调度执行,线程是操作系统调度执行的基本单位,(前面讲的时间管理,当时咱们是调度的进程,但是更准确的说,其实是调度的线程
    • 调度的进程:前面的例子相当于是每个进程里,只有一个线程了,可以视为是在调度进程,但是如果进程里有多个线程,更严谨的说法,还是以线程为单位进行调度
  4. 进程具有独立性每个进程有各自的虚拟地址空间,一个进程挂了,不会影响到其他进程。
    同一个进程中的多个线程,共用同一个内存空间,一个线程挂了,可能影响到其他线程的,甚至导致整个进程崩溃

Java这个生态中更常使用的并发编程方式,是多线程
其他的语言,主打的并发变成又不一样:
go,主要是通过多协程的方式实现并发.
erlang,这个是通过 actor 模型实现并发.
JS,是通过定时器+事件回调的方式实现并发.……
多线程仍然是最主流最常见的一种并发编程的方式


五、Java 多线程编程

Java 的线程 和 操作系统线程 的关系:

  1. 线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户层提供了一些 API 供用户使用 (例如 Linux 的 pthread 库),
    在 Java 标准库 中,就提供了一个 Thread 类,来表示 / 操作线程,Thread 类可以视为 Java 标准库提供的 API, 对操作系统提供的 API 进行了进一步的抽象和封装
  2. 创建好的 Thread实例,其实和操作系统中的线程是一 一对应的关系,操作系统提供了一组关于线程的API(C语言风格),Java对于这组API进一步封装了,就成了Thread

1、第一个多线程程序

Thread类的基本用法
通过 Thread 类创建线程,写法有很多种
其中最简单的做法,创建子类,继承自Thread,并且重写 run 方法

package thread;

class MyThread extends Thread 
    @Override
    public void run() 
        System.out.println("hello thread!");;
    


public class demo1 
    public static void main(String[] args) 
        Thread t = new MyThread();
        t.start();
    

run 方法描述了,这个线程内部要执行哪些代码每个线程都是并发执行的 (各自执行各自的代码),因此就需要告知这个线程,你执行的代码是什么
run 方法中的逻辑,是在新创建出来的线程中,被执行的代码

并不是我一定义这个类,一写run方法,线程就创建出来,相当于我把活安排出来了,但是同学们还没开始干呢
需要调用这里的 start 方法,才是真正的在系统中创建了线程,才是真正开始执行上面的 run 操作,在调用 start 之前,系统中是没有创建出线程的


2、线程之间是并发执行的

如果在一个循环中不加任何限制,这个循环转的速度非常非常快,导致打印的东西太多了,根本看不过来了,就可以加上一个 sleep 操作,来强制让这个线程休眠一段时间
这个休眠操作,就是强制地让线程进入阻塞状态,单位是 ms,就是1s 之内这个线程不会到 cpu 上执行

public void run() 
   while (true) 
        System.out.println("hello thread!");
        Thread.sleep(1000);
    

这是多线程编程中最常见的一个异常,线程被强制的中断了,用 try catch 处理

在一个进程中,至少会有一个线程,
在一个 java进程中,也是至少会有一个调用 main 方法的线程 (这个线程不是你手动搞出来的)
自己创建的 t 线程 和 自动创建的 main 线程,就是并发执行的关系 (宏观上看起来是同时执行)
此处的并发 = 并行 + 并发
宏观上是区分不了并行和并发的,都取决于系统内部的调度

package thread;

// Thread是在java.lang 里的,java.lang 里的类都不需要手动导入,类似的还有String
class MyThread2 extends Thread 
    @Override
    public void run() 
        while (true) 
            System.out.println("hello thread!");
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    


public class demo2 
    public static void main(String[] args) 
        Thread t = new MyThread2();
        t.start();

        while (true) 
            System.out.println("hello main");
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

运行打印:

/* hello main
hello thread!
hello thread!
hello main
hello main
hello thread!
hello thread!
hello main */

现在两个线程,都是打印一条,就休眠 1s
当1s 时间到了之后,系统先唤醒谁呢?
看起来这个顺序不是完全确定 (随机的)
每一轮,1s 时间到了之后,到底是先唤醒 main 还是 thread,这是不确定的 (随机的)
操作系统来说,内部对于线程之间的调度顺序,在宏观上可以认为是随机的 (抢占式执行)
这个随机性,会给多线程编程带来很多其他的麻烦


3、Thread 类创建线程的写法

写法一: 创建子类,继承自 Thread

写法二: 创建一个类,实现 Runnable 接口,再创建 Runnable实例传给Thread 实例

通过 Runnable 来描述任务的内容
进—步的再把描述好的任务交给Thread 实例

package thread;

// Runnable 就是在描述一个任务
class MyRunnable implements Runnable 
    @Override
    public void run() 
        System.out.println("hello");
    


public class demo3 
    public static void main(String[] args) 
        Thread t = new Thread(new MyRunnable());
    

写法三 / 写法四: 就是上面两个写法的翻版,使用了匿名内部类

创建了一个匿名内部类,继承自 Thread 类,同时重写run方法,同时再new出这个匿名内部类的实例

package thread;

public class demo4 
    public static void main(String[] args) 
        Thread t = new Thread() 
            @Override
            public void run() 
                System.out.println("hello thread!");
            
        ;
        t.start();
    

new 的是Runnable,针对这个创建的匿名内部类,同时new 出的 Runnable` 实例传给 Thread 的构造方法

package thread;

public class demo5 
    public static void main(String[] args) 
        Thread t = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("hello thread!");
            
        );
        t.start();
    

通常认为Runnable 这种写法更好一点,能够做到让线程和线程执行的任务,更好的进行解耦
写代码一般希望,高内聚,低耦合
Runnable 单纯的只是描述了一个任务,至于这个任务是要通过一个进程来执行,还是线程来执行,还是线程池来执行,还是协程来执行,Runnable 本身并不关心,Runnable 里面的代码也不关心

第五种写法: 相当于是第四种写法的延伸,使用 lambda 表达式,是使用lambda 代替了 Runnable 而已

package thread;

public class demo6 
    public static void main(String[] args) 
        Thread t = new Thread(() -> 
            System.out.println("hello thread!");
        );
        t.start();
    


多线程能够提高任务完成的效率

测试:有两个整数变量,分别要对这俩变量自增10亿次,分别使用一个线程,和两个线程

此处不能直接这么记录结束时间,别忘了,现 在这个求时间戳的代码是在 main 线程中
maint1t2 之间是并发执行的关系,此处t1t2 还没执行完呢,这里就开始记录结束时间了,这显然是不准确的
正确做法应该是main线程等待 t1t2 跑完了,再来记录结束时间
join 效果就是等待线程结束t1.join就是让main 线程等待t1 结束,t2.joinmain 线程等待 t2结束

package thread;

public class demo7 
    private static final long count = 10_0000_0000;

    public static void serial() 
        long begin = System.currentTimeMillis();
        long a = 0;
        for (int i = 0; i < count; i++) 
            a++;
        
        long b = 0;
        for (int i = 0; i < count; i++) 
            b++;
        
        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end- begin) + "ms");
    

    public static void concurrency() throws InterruptedException 
        long begin = System.currentTimeMillis();
        Thread t1 = new Thread(() -> 
            long a = 0;
            for (int i = 0; i < count; i++) 
                a++;
            
        );
        t1.start();
        Thread t2 = new Thread(() -> 
            long b = 0;
            for (int i = 0; i < count; i++) 
                b++;
            
        );
        t2.start();

        t1.join(); // 让 main 线程等待 t1 结束
        t2.join(); // 让 main 线程等待 t2 结束

        long end = System.currentTimeMillis();
        System.out.println("消耗时间: " + (end- begin) + "ms");
    

    public static void main(String[] args) throws InterruptedException 
        // serial();
        concurrency();
    

串行执行的时候,时间大概是600多ms (平均650左右)
两个线程并发执行,时间大概是400多ms (平均450左右)
提升了接近50%
并不是说,一个线程600多ms,两个线程就是300多ms
这俩线程在底层到底是并行执行,还是并发执行,不确定,真正并行执行的时候,效率才会有显著提升

多线程特别适合于那种CPU密集型的程序,程序要进行大量的计算,使用多线程就可以更充分的利用CPU的多核资源


六、Thread类的其他的属性和方法

1、Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理,分好的组即为线程组,这 个目前我们了解即可

Thread(String name):这个东西是给线程 (thread对象) 起一个名字,起一个啥样的名字,不影响线程本身的执行
仅仅只是影响到程序猿调试,可以借助一些工具看到每个线程以及名字,很容易在调试中对线程做出区分

可以使用 jconsole观察线程的名字jconsolejdk自带的一个调试工具


jconsole 这里能够罗列出你系统上的java进程(其他进程不行)

以上是关于计算机是如何工作的,Java多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

Java 多线程进阶-并发编程 线程组ThreadGroup

Java多线程编程(基础篇)

Java线程—1

GolangGoroutine原理

多线程是啥

线程(概念篇)