并发编程中的一些基础知识

进程和线程

进程

进程是程序运行的基本单位。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

在 Java 中,我们启动 main 函数时其实就是启动了一个 JVM 的线程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

线程

线程是比进程更小的程序执行单位,一个进程在其执行过程中可以产生多个线程。

同一个进程中的多个线程共享一个 方法区,但每个线程有自己 独立程序计数器(PC)、虚拟机栈本地方法栈

二者的区别和联系

线程是进程划分成的更小的运行单位。线程和进程最大的不同在于**各进程基本上是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。**线程执行开销小,但不利于资源的管理和保护;而进程正相反。

线程之间的切换所产生的负担比进程之间的切换要小得多,原因如下:

  • 共享资源:同一进程内的线程共享同一个地址空间、文件描述符和其他资源,不需要像进程切换那样重新加载内存映射和管理数据结构。

  • 上下文保存量少:线程切换只需要保存少量寄存器状态(例如程序计数器、栈指针等),而进程切换需要保存整个进程的上下文,并且往往还需要刷新TLB和切换虚拟内存环境。

    img

Java 线程与操作系统线程的区别和联系

用户线程和内核线程

用户线程创建和切换成本低,但无法利用多核,运行在用户空间。

内核线程创建和切换成本高,但可以利用多核,运行在内核空间。

Java Thread

JDK 1.2 以前,Java的多线程实质上是JVM模拟的多线程运行,并不依赖于操作系统。这种模拟的多线程使得模拟的多线程只能够在一个内核线程上运行。这是一种用户线程

JDK 1.2 及以后,Java线程改为基于原生线程(Naive Thread)实现,也就是JVM直接调用操作系统的原生线程来实现Java线程,由操作系统内核进行线程的调度及管理。这是一种内核线程

线程的生命周期

img

当使用 new 关键字新建一个 Thread,这个 Thread 就进入了新建状态。

当调用这个 Thread 的 start() 方法时,系统就会启动另一个线程并使其进入就绪状态。

当分配到时间片后,这个 Thread 就会开始运行。系统会自动调用该线程 Runnable 接口的 start() 方法。

注意:如果我们直接调用 Thread 的 run() 方法,那么系统就会将其当成一个 main 线程下的普通方法去执行,并不会在某个其他的线程中执行,所以这并不是多线程工作。

Object.wait()Thread.sleep() 的区别

方法 Thread.sleep() Object.wait()
Thread Object
锁的影响 不释放锁 释放锁并进入等待队列
线程间通信 不支持线程间通信 用于线程间通信,通过 notify()notifyAll() 唤醒
异常 会抛出 InterruptedException 会抛出 InterruptedException
使用场景 使线程暂停执行一段时间,通常用于延迟 用于线程间同步和协作,线程进入等待直到被唤醒

区别并发和并行

并发 Concurrent:两个及以上线程在同一时间段执行 (注意是同一时间段,并不是同一时刻)

并行 Parallel:两个及以上线程在同一时刻执行 (只有在多核 CPU 中才存在并行的概念)

我们常说的多线程,其实是指在一台机器上并发、串行地执行任务。

多线程执行可能带来的问题

线程不安全

线程不安全指的是多个线程并发访问共享数据时,程序没有采取适当的同步措施,导致某些数据在访问过程中被破坏,产生不一致的结果。

(补充:线程安全 指的是在多线程环境下,对于同一份数据,不管有多少个线程同时访问,都能保证这份数据的 正确性一致性。)

内存泄漏(和内存溢出要区分开)

内存泄漏是指程序在运行过程中动态分配了内存,但没有正确释放这些内存,导致内存无法回收,占用更多系统资源,使系统性能下降甚至崩溃。

死锁

死锁是指两个或多个线程在执行过程中,由于 每个线程都在等待某个资源被释放,导致程序进入无法继续执行的状态。

死锁产生的四个必要条件 :互斥操作、不可剥夺、循环等待、请求和保持

预防死锁

  • 破坏请求与保持:一次性申请所有资源
  • 破坏不可剥夺:当一个占有资源的线程申请其他资源不成功时,令其主动释放其拥有的资源
  • 破坏循环等待:按一定顺序申请资源

避免死锁:事先评估(银行家算法 安全路径)


并发编程中的一些基础知识
http://example.com/2025/03/12/并发编程中的一些基础知识/
作者
Moonike
发布于
2025年3月12日
许可协议