引言

现如今大部分处理器都拥有多个核心,单线程程序无法充分发挥硬件性能。同时在实际的互联网应用中,高并发的场景随处可见:电商APP的抢购、社交平台信息的及时推送、大数据系统的批量处理等。本文将从Java开发的角度,记录和梳理并发编程相关的知识点。

多线程

多线程是指在一个程序中同时运行多个线程,这些线程共享程序的内存空间(如全局变量、方法区)但是拥有各自独立的线程栈和程序计数器。在进行多线程开发的时候需要注意几个问题:

  • 线程安全:多个线程同时对共享资源进行操作时,可能出现错误。例如多个线程同时处理用户下单的请求,可能出现线程A读取库存为10,线程B也读取库存为10,随后两者都执行库存减1的操作,最终库存变为9而不是8。
  • 线程间通信:进程间需要协作完成一项任务时,需要通过一定的方法实现线程间通信。例如线程A创建订单,线程B负责同步订单信息安排发货。每当有订单创建时,需要通过通信机制告知线程B及时处理,同时也需要避免没有订单时线程B空等。
  • 线程管理:多线程的管理核心是线程生命周期管控:创建线程需分配资源,频繁创建销毁会消耗系统开销;线程切换(上下文切换)会保存/恢复线程状态,切换过频会拖累性能;还需避免线程泄漏(创建后未正常销毁)、线程数量失控(过多线程抢占资源)等问题。

线程安全

JMM模型

JAVA的内存模型提供了一套规则,用于解决多线程并发的安全问题,核心处理的问题有三点:

  • 可见性:一个线程修改共享变量时,其他线程可以立刻看到最新值
  • 原子性:操作要么完全执行,要么完全不执行,没有中间状态
  • 有序性:程序执行顺序与代码编写顺序一致(避免编译器/CPU为优化而重排指令,导致并发时逻辑错乱)

保证线程安全的方法

synchronized

synchronized 关键字可以用来同步代码块或者方法,确保同一时间只有一个线程可以访问这些代码。从Java语义来说,synchronized锁定的是对象的监视器(Monitor),每一个Java对象天生关联一个Monitor,线程需要获取Monitor才能执行同步代码,因此本质上是通过Monitor来实现锁竞争与互斥。从物理存储的实现来看,Monitor的持有状态会记录在对象头中(如Mark Word字段),通过修改对象头的标记来标识当前锁的所有者、锁状态(偏向锁/轻量级锁/重量级锁)。

volatile

volatile关键字用于变量,核心作用是保证变量的可见性有序性,但是不能保证原子性。

  • 可见性:线程修改volatile修饰的变量后,会立刻刷新到主存中,同时令其他线程缓存中的该变量副本过期,再次读取时需要从主存中加载最新值。
  • 有序性:volatile关键字会禁止编译器和CPU重排与之相关的指令,确保代码执行顺序和编写顺序一致。 但是,它仍无法解决符合操作的原子性问题,仍可能存在符合操作被其他线程打断的问题。

Java线程的状态

线程状态 核心说明
NEW 线程已创建但未调用 start() 方法,尚未启动执行。
RUNNABLE 线程调用 start() 后进入,包含“就绪”(等待CPU调度)和“运行中”(正在执行)两种状态。
BLOCKED 线程因竞争同步锁(如 synchronized)被阻塞,等待锁释放。
WAITING 线程通过 wait()join() 等方法主动等待,需其他线程通知(notify())才能唤醒。
TIMED_WAITING 线程通过 sleep(long)wait(long) 等方法限时等待,超时后自动唤醒。
TERMINATED 线程执行完毕(run() 方法结束)或异常终止,生命周期结束。