专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Java多线程基础全面解析:线程概念、生命周期与高频面试知识总结

1.基本概念

1.1程序、进程、线程基本概念

程序

程序是为了完成特定任务、用某种语言编写的一组指令的集合,即:指代一段静态代码,静态对象

进程

线程是程序的一次执行过程,或是正在运行的一个程序,是操作系统进行资源分配和调度的基本单位。是一个动态的过程。进程具有自身的生命周期(产生、存在和消亡的过程)

线程

一个进程存在一个或多个线程,是进程的一个执行单元,是一个程序内部的一条执行路径

总结

1、 程序是编程指令或语言组成的集合(静态的),进程是正在运行的程序(动态的),线程是进程中的一条执行路径(动态的)
2、 进程是资源分配的基本单位,线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC)
3、 一个进程如果可以同一时间并行执行多个线程,则是支持多线程的
4、 一个进程中的多个线程共享相同的内存单元/内存地址空间(也就是共享堆空间),可以访问相同的变量和对象
5、 共享堆空间优缺点:

**优点**:线程间通信更简便、高效;

**缺点**:多个线程操作共享的系统资源可能会带来安全隐患

img_1

1.2 多核CPU和单核CPU

单核CPU:

单核CPU处理多线程的情况下,实际上是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务

img_2

多核CPU:

几个核之间并行执行线程,才是实际上的多线程

img_3

知识点:一个Java应用程序,至少有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程。异常发生会影响主线程

1.3 并发和并行

并发:

一个CPU同时执行多个任务–这里涉及到CPU的时间片问题。 进程在执行的过程中,如果存在多个任务,操作系统会按照时间片的划分,给每个线程一定的执行时间,确保同一时间只有一个线程执行。

即:当A线程执行到时间片结束后换线程B继续执行,交替直到结束

img_4

并行:

多核CPU执行多线程任务,每个核心独立运行一个线程,多个线程可以同时执行。

img_5

1.4 多线程的三大特性

  • 可见性:一个线程对共享变量的修改,另一个线程能够立即看到
  • 原子性:一个操作或者多个操作,要么全部执行,并且执行过程中不会被任何因素打断,要么就都不执行
  • 有序性:程序执行的顺序按照代码的先后顺序执行

1.5 线程的分类

Java中的线程分为两类:

  • 守护线程:

  • 守护线程是为用户线程提供服务的线程,通过在start()方法之前调用thread.setDaemon(true)可以把一个用户线程变成守护线程

    垃圾回收线程就是一个典型的守护线程

    守护线程是否执行不影响JVM的退出,当所有用户线程执行完毕后,JVM才会自动退出。

  • 用户线程

  • 程序创建并管理的线程。

    JVM在运行时会等待所有用户线程执行完毕后才会退出

1.6 线程的生命周期

1.6.1 操作系统中线程的生命周期

img_6

线程的五种状态:

  • 新建:当一个线程被创建时,它处于新建状态,此时线程对象已经被实例化,但是还没有开始执行
  • 就绪:当处于新建状态的线程调用了启动方法(例如Java中的start()),该线程进入就绪状态,将进入线程队列等待系统调度分配CPU时间片,此时已经具备了运行的条件,但是没有分配到CPU资源
  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊的情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,将会进入阻塞状态,当阻塞结束后会重新进入就绪状态准备抢占CPU资源获取运行机会
  • 死亡:线程完成了它的全部工作或线程被提前强制性地终止或出现异常导致结束

1.6.2 Java线程生命周期

Java线程的生命周期被分为6种

img_7

  • 新建:创建后尚未启动(没用调用start())
  • 可运行:可能正在运行,也可能正在等待CPU时间片【处于操作系统的就绪态或运行态】
  • 锁阻塞(Blocking) :等待获取一个排他锁,如果其线程释放了,锁就会结束此状态
  • 无限期等待(Waiting) :等待其他线程显式唤醒,否则不会被分配CPU时间片
进入无限期等待方法 退出无限期等待方法
没有设置 Timeout 参数的 Object.wait() 方法  Object.notify() / Object.notifyAll() 
没有设置 Timeout 参数的 Thread.join() 方法  被调用的线程执行完毕 
LockSupport.park()方法  – 
  • 限期等待(Timed Waiting) :无需等待其他线程显式唤醒,在一定时间之后会被系统自动唤醒。

    调用Thread.sleep() 方法使线程进入限期等待状态时。

    调用 Object.wait() 方法使线程进入限期等待或者无限期等待时

进入限期等待方法 退出限期等待方法
Thread.sleep() 方法  时间结束 
设置了 Timeout 参数的 Object.wait() 方法  时间结束 / Object.notify() / Object.notifyAll() 
设置了 Timeout 参数的 Thread.join() 方法  时间结束 / 被调用的线程执行完毕 
LockSupport.parkNanos() 方法  – 
LockSupport.parkUntil() 方法  – 
  • 死亡(Terminated) :可以是线程结束任务之后自己结束,也可以是产生了异常而结束

2. 为什么需要多线程

2.1 多线程的出现

CPU、内存、I/O设备的速度是极大差异的,为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了一定的调整

  • CPU增加了缓存,用来均衡与内存之间的速度差异
  • 会导致可见性问题
//线程1执行的代码
    int i = 0;
    i = 10;
    //线程2执行的代码
    j = i;
CPU1执行线程1;CPU2执行线程2

当线程1执行i=10,会先把i的初始值0加载到CPU1的高速缓存中,然后赋值为10,此时的高速缓存中i的值变成了10,但是却**没有立即写入主存中**

此时线程2执行j=i,CPU2会**先去主存读取i的值**,并**加载到高速缓存中**,此时i还是0就会导致j=0,而实际应该是j=10
  • 操作系统增加了进程、线程,用于分时复用CPU,进而均衡CPU和I/O设备的速度差异
  • 会导致原子性问题
int i = 1;
    // 线程1执行
    i += 1;
    // 线程2执行
    i += 1;
【i+=1需要执行三条CPU指令】,三条指令如下:

由于CPU的**分时复用机制**,CPU在执行线程1的第一条指令后,切换到线程2执行,如果线程2执行了三条指令再切换到线程1执行后两条指令,将会造成最后写入内存中的i值是2,而不是3
  • 将变量i从内存读取到CPU高速缓存

    执行i+1的操作

    将结果i写入内存(由于CPU的缓存机制,存储的可能是缓存)

  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用

  • 导致有序性问题

int i = 0;              
    boolean flag = false;
    i = 1;                //语句1  
    flag = true;          //语句2
从代码上看,语句1会在语句2前面执行,但是在JVM层面,这段代码不一定是语句1先执行,这里可能会发生

2.2 多线程程序的优点

1、 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2、 提高计算机系统CPU的利用率。
3、 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。

3.多线程常用方法

多线程常用方法解析:

start() :启动当前线程;调用当前线程的run()

run() :通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

currentThread() :静态方法,返回执行当前代码的线程

getName() :获取当前线程的名字

setName() :设置当前线程的名字

yield() :释放当前CPU的执行权

join() :在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态;当前线程对象调用,就让这个对象的线程执行完以后才让其他线程加入

stop() :已过时。当执行此方法时,强制结束当前线程

sleep(long millitime) :让当前线程“睡眠”指定的millitime毫秒数,在指定的毫秒时间内,当前的线程处于阻塞状态(不会自动释放锁)

isAlive() :判断当前线程是否存活

wait() :执行此方法时,使线程进入等待状态(阻塞),阻塞时线程会自动释放锁。

notify() :一旦执行此方法,就会唤醒被wait阻塞的线程(只能唤醒一个),如果有多个阻塞线程,优先唤醒优先级较高的线程

notifyAll() :一旦执行此方法,将会唤醒所有被阻塞(wait)的线程

方法说明

  • sleepyield的区别

  • sleep可以使优先级低的线程得到执行的机会

    yield只能使同优先级的线程有执行的机会

  • interrupt方法不会中断一个正在运行的线程。就是指线程如果正在运行的过程中,去调用此方法是没有任何反应的.。因为这个方法只是提供给被阻塞的线程,即当线程调用了Object.wait()Thread.join()Thread.sleep()三种方法之一的时候,再调用interrupt方法,才可以中断刚才的阻塞而继续去执行线程。

  • join()当join(0)时等待一个线程执行直到它死亡返回主线程,当join(1000)时主线程等待一个线程1000纳秒,后回到主线程继续执行

  • waite()和notify()必须在synchronized方法或synchronized块中进行调用。如果在non-synchronized方法函数或non-synchronized块中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

4. 多线程的使用场景

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。
未经允许不得转载:搜云库技术团队 » Java多线程基础全面解析:线程概念、生命周期与高频面试知识总结

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们