多线程、进程与JVM 技术笔记
1 进程与线程核心概念
1.1 进程
进程是操作系统分配资源的最小单位,是程序的一次执行过程。操作系统以进程为单位分配内存空间、文件句柄、I/O设备等资源,不同进程之间相互独立,数据不共享,通信需要依靠专门的进程间通信机制。进程的创建、切换与销毁开销较大,一个程序运行至少对应一个进程。
1.2 线程
线程是CPU调度与执行的最小单位,依附于进程存在。一个进程内部可以包含一个或多个线程,同一进程内的所有线程共享进程的堆、方法区、文件描述符等资源,每个线程拥有独立的程序计数器、虚拟机栈和本地方法栈。线程切换开销远小于进程,一个进程至少包含一个主线程。
1.3 进程与线程关键差异
进程是资源分配最小单位,线程是CPU执行最小单位。进程间内存相互独立,线程间共享所属进程内存。进程切换开销大,线程切换开销小。进程间通信复杂,线程间通信简单直接。进程稳定性更高,一个线程异常可能导致整个进程崩溃。
2 Java线程的三种创建方式
2.1 继承Thread类
自定义类继承Thread类,重写run方法编写线程执行逻辑。创建该类对象后调用start方法启动线程,直接调用run方法只会执行普通方法,不会开启新线程。该方式受Java单继承限制,无法再继承其他父类。
2.2 实现Runnable接口
自定义类实现Runnable接口,重写run方法。将该类实例作为参数传入Thread构造器,通过Thread对象启动线程。该方式避免单继承局限,适合多个线程共享同一份业务逻辑。
2.3 实现Callable接口
自定义类实现Callable接口,重写call方法。call方法支持返回值并可抛出异常,需要借助FutureTask包装后交给Thread启动,通过FutureTask的get方法获取执行结果,常配合线程池使用。
3 线程安全问题
3.1 线程不安全的原因
多个线程同时读写共享变量,操作不具备原子性、变量修改不具备可见性、指令重排导致执行顺序异常,都会引发线程安全问题。
3.2 线程安全三大特性
原子性:一个操作不可中断,要么全部执行,要么全部不执行。
可见性:一个线程修改共享变量后,其他线程能立即看到最新值。
有序性:程序执行顺序与代码顺序一致,禁止指令重排。
3.3 线程安全解决方案
3.3.1 synchronized关键字
隐式锁,可修饰实例方法、静态方法或代码块,保证原子性、可见性、有序性,同一时刻仅一个线程进入同步区域。
3.3.2 Lock接口
显式锁,常用实现类为ReentrantLock,支持手动加锁、解锁,可响应中断、支持超时获取锁与公平锁,解锁必须放在finally中保证执行。
3.3.3 volatile关键字
轻量级同步机制,保证可见性与禁止指令重排,但不保证原子性,适合状态标记场景。
3.3.4 原子类
基于CAS无锁机制实现原子操作,如AtomicInteger、AtomicLong等,适合简单数值更新。
3.4 死锁
死锁是多个线程互相持有对方所需锁且不释放,导致程序无限阻塞。产生需同时满足互斥、请求与保持、不可剥夺、循环等待四个条件。避免死锁可统一加锁顺序、设置锁超时、避免嵌套锁、使用定时锁。
4 Java内存模型与线程可见性
4.1 Java内存模型作用
屏蔽硬件与操作系统内存访问差异,保证Java程序在不同平台下内存访问效果一致。
4.2 主内存与工作内存
主内存为所有线程共享,存储实例对象、静态变量等数据。工作内存为线程私有,保存线程使用的主内存变量副本。
4.3 可见性问题
线程修改工作内存副本后未及时同步回主内存,其他线程读取到旧值。可通过volatile、synchronized、Lock解决可见性问题。
5 生产者-消费者模型
5.1 模型作用
经典线程协作模式,生产者生产数据放入缓冲区,消费者从缓冲区取出数据处理,实现解耦、削峰、平衡处理速度。
5.2 核心机制
基于阻塞队列与等待唤醒机制。队列满时阻塞生产者,队列空时阻塞消费者。可通过synchronized+wait/notify、Lock+Condition或JUC阻塞队列实现。
6 JVM与操作系统
6.1 操作系统作用
操作系统是管理计算机硬件的系统软件,负责调度CPU、内存、I/O设备等资源,为上层程序提供运行环境。C/C++程序可直接编译为机器码运行在操作系统上。
6.2 JVM核心作用
JVM即Java虚拟机,相当于微型操作系统,Java程序不直接运行在操作系统,而是运行在JVM中,实现一次编译、到处运行。
6.3 Java程序运行流程
Java源文件编译为.class字节码文件,字节码由JVM解释或编译为对应平台机器码,JVM通过本地方法库调用操作系统底层能力,最终由操作系统调度硬件执行指令。JVM本身对应操作系统的一个进程,内部管理多个Java线程。