
在当今的软件开发中,并发编程已经成为一项必不可少的技能。随着多核处理器的普及,充分利用系统资源、提高程序运行效率变得越来越重要。Java 提供了强大的并发编程支持,使得开发者能够编写出高效的多线程程序。本章将详细介绍 Java 并发编程的基础知识,包括线程的创建、状态管理、同步机制以及常用的并发工具类。
多线程是指在一个程序中同时运行多个独立的执行流(线程),每个线程可以执行不同的任务。与单线程相比,多线程具有以下优势:
在 Java 中,线程是程序执行的最小单位,一个进程可以包含多个线程,这些线程共享进程的资源,但拥有各自的执行栈和程序计数器。

多线程的应用场景:
在 Java 中,创建线程主要有两种方式:实现 Runnable 接口和继承 Thread 类。
Runnable 接口只有一个 run() 方法,用于定义线程要执行的任务。实现 Runnable 接口后,需要将其实例传递给 Thread 类的构造方法,然后调用 start() 方法启动线程。
/**
* 实现 Runnable 接口创建线程示例
*/
public class RunnableDemo implements Runnable {
private String threadName;
public RunnableDemo(String name) {
threadName = name;
System.out.println("创建线程: " + threadName);
}
// 线程执行的任务
public void run() {
System.out.println("启动线程: " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("线程 " + threadName + ": " + i);
// 让线程休眠一段时间
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("线程 " + threadName + " 被中断");
}
System.out.println("线程 " + threadName + " 退出");
}
public static void main(String args[]) {
// 创建 Runnable 实例
RunnableDemo R1 = new RunnableDemo("线程-1");
// 创建 Thread 实例,并将 Runnable 实例作为参数传入
Thread t1 = new Thread(R1);
RunnableDemo R2 = new RunnableDemo("线程-2");
Thread t2 = new Thread(R2);
// 启动线程
t1.start();
t2.start();
}
}
另一种创建线程的方式是继承 Thread 类,并重写其 run() 方法。然后创建该子类的实例并调用 start() 方法启动线程。
/**
* 继承 Thread 类创建线程示例
*/
public class ThreadDemo extends Thread {
private String threadName;
public ThreadDemo(String name) {
threadName = name;
System.out.println("创建线程: " + threadName);
}
// 重写 run() 方法,定义线程执行的任务
public void run() {
System.out.println("启动线程: " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("线程 " + threadName + ": " + i);
// 让线程休眠一段时间
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("线程 " + threadName + " 被中断");
}
System.out.println("线程 " + threadName + " 退出");
}
public static void main(String args[]) {
// 创建线程实例
ThreadDemo t1 = new ThreadDemo("线程-1");
ThreadDemo t2 = new ThreadDemo("线程-2");
// 启动线程
t1.start();
t2.start();
}
}
两种方式的比较:
实现 Runnable 接口 | 继承 Thread 类 |
|---|---|
可以继承其他类 | 不能再继承其他类 |
适合多个线程共享资源 | 资源共享需要额外处理 |
代码结构更清晰,符合面向接口编程思想 | 代码相对简单直接 |
每个 Java 程序都有一个默认的主线程,即 main() 方法所在的线程。主线程是程序的入口点,负责启动其他线程。
/**
* 主线程示例
*/
public class MainThreadDemo {
public static void main(String[] args) {
// 获取当前线程(主线程)
Thread mainThread = Thread.currentThread();
System.out.println("主线程名称: " + mainThread.getName());
System.out.println("主线程优先级: " + mainThread.getPriority());
// 修改主线程名称
mainThread.setName("MyMainThread");
System.out.println("修改后主线程名称: " + mainThread.getName());
// 启动一个新线程
Thread childThread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程运行中: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
childThread.start();
// 主线程执行任务
for (int i = 0; i < 5; i++) {
System.out.println("主线程运行中: " + i);
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程执行完毕");
}
}
Java 中的线程有以下几种状态,这些状态定义在 Thread.State 枚举中:
start() 方法线程状态转换示例:
/**
* 线程状态示例
*/
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("3. 线程运行中,状态: " + Thread.currentThread().getState());
try {
// 线程进入超时等待状态
Thread.sleep(1000);
// 等待另一个线程的通知
synchronized (this) {
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("6. 线程即将结束,状态: " + Thread.currentThread().getState());
}
});
// 新建状态
System.out.println("1. 线程创建后,状态: " + thread.getState());
// 启动线程
thread.start();
Thread.sleep(100); // 等待线程启动
System.out.println("2. 线程启动后,状态: " + thread.getState());
// 等待线程进入超时等待状态
Thread.sleep(200);
System.out.println("4. 线程休眠中,状态: " + thread.getState());
// 等待线程进入等待状态
Thread.sleep(1000);
System.out.println("5. 线程等待中,状态: " + thread.getState());
// 唤醒等待的线程
synchronized (thread) {
thread.notify();
}
// 等待线程结束
thread.join();
System.out.println("7. 线程结束后,状态: " + thread.getState());
}
}
Java 线程有优先级,范围从 1 到 10,默认优先级为 5。优先级高的线程获得 CPU 资源的概率更高,但这并不意味着一定先执行。
线程调度由 Java 虚拟机(JVM)和操作系统共同完成,Java 提供了两种调度模型:
/**
* 线程优先级示例
*/
public class ThreadPriorityDemo {
public static void main(String[] args) {
// 创建三个线程
Thread highPriority = new Thread(new Counter("高优先级线程"));
Thread mediumPriority = new Thread(new Counter("中优先级线程"));
Thread lowPriority = new Thread(new Counter("低优先级线程"));
// 设置线程优先级
highPriority.setPriority(Thread.MAX_PRIORITY); // 10
mediumPriority.setPriority(Thread.NORM_PRIORITY); // 5
lowPriority.setPriority(Thread.MIN_PRIORITY); // 1
// 启动线程
lowPriority.start();
mediumPriority.start();
highPriority.start();
}
static class Counter implements Runnable {
private String name;
public Counter(String name) {
this.name = name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + ":" + i + ",优先级:" + Thread.currentThread().getPriority());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程结束的方式有以下几种:
run() 方法执行完毕interrupt() 方法中断线程/**
* 线程结束控制示例
*/
public class ThreadTerminationDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个可以被中断的线程
Thread interruptibleThread = new Thread(new InterruptibleTask());
interruptibleThread.start();
// 运行一段时间后中断线程
Thread.sleep(2000);
System.out.println("主线程:请求中断子线程");
interruptibleThread.interrupt();
// 等待线程结束
interruptibleThread.join();
System.out.println("主线程:子线程已结束");
}
static class InterruptibleTask implements Runnable {
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("子线程:正在执行任务...");
Thread.sleep(500); // 模拟任务执行
}
System.out.println("子线程:收到中断请求,准备结束");
} catch (InterruptedException e) {
System.out.println("子线程:在休眠中被中断");
// 恢复中断状态
Thread.currentThread().interrupt();
} finally {
System.out.println("子线程:执行清理工作");
}
System.out.println("子线程:已结束");
}
}
}
注意:不推荐使用
stop()、suspend()和resume()方法来控制线程,这些方法已经被标记为过时,可能导致资源泄露或线程死锁等问题。
当多个线程访问共享资源时,如果不加以控制,可能会导致数据不一致的问题,这就是线程冲突。
原子操作是指不可分割的操作,要么全部执行,要么都不执行。Java 提供了 java.util.concurrent.atomic 包来支持原子操作。
/**
* 线程冲突示例
*/
public class ThreadConflictDemo {
// 共享资源
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 创建两个线程,同时对 count 进行递增操作
Thread thread1 = new Thread(new IncrementTask());
Thread thread2 = new Thread(new IncrementTask());
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
// 预期结果是 20000,但实际可能小于这个值
System.out.println("最终计数:" + count);
}
static class IncrementTask implements Runnable {
public void run() {
for (int i = 0; i < 10000; i++) {
// 非原子操作:读取 -> 修改 -> 写入
count++;
}
}
}
}
上面的代码中,count++ 不是原子操作,它包含三个步骤:读取 count 的值、将值加 1、将结果写回 count。当两个线程同时执行时,可能会导致计数不正确。
使用 synchronized 关键字修饰方法,可以保证同一时间只有一个线程执行该方法,从而避免线程冲突。
/**
* 同步方法示例
*/
public class SynchronizedMethodDemo {
// 共享资源
private int count = 0;
public static void main(String[] args) throws InterruptedException {
SynchronizedMethodDemo demo = new SynchronizedMethodDemo();
// 创建两个线程,同时对 count 进行递增操作
Thread thread1 = new Thread(new IncrementTask(demo));
Thread thread2 = new Thread(new IncrementTask(demo));
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
// 使用同步方法后,结果应该是 20000
System.out.println("最终计数:" + demo.count);
}
// 同步方法
public synchronized void increment() {
count++;
}
static class IncrementTask implements Runnable {
private SynchronizedMethodDemo demo;
public IncrementTask(SynchronizedMethodDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 0; i < 10000; i++) {
demo.increment();
}
}
}
}
对于静态同步方法,锁是当前类的 Class 对象。
/**
* 静态同步方法示例
*/
public class StaticSynchronizedMethodDemo {
// 静态共享资源
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
// 创建两个线程,同时对 count 进行递增操作
Thread thread1 = new Thread(new StaticIncrementTask());
Thread thread2 = new Thread(new StaticIncrementTask());
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
System.out.println("最终计数:" + count);
}
// 静态同步方法
public static synchronized void staticIncrement() {
count++;
}
static class StaticIncrementTask implements Runnable {
public void run() {
for (int i = 0; i < 10000; i++) {
StaticSynchronizedMethodDemo.staticIncrement();
}
}
}
}
除了同步整个方法,还可以使用 synchronized 关键字同步代码块,这样可以只同步关键部分,提高程序效率。
/**
* 同步块示例
*/
public class SynchronizedBlockDemo {
// 共享资源
private int count = 0;
// 锁对象
private Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
SynchronizedBlockDemo demo = new SynchronizedBlockDemo();
// 创建两个线程,同时对 count 进行递增操作
Thread thread1 = new Thread(new BlockIncrementTask(demo));
Thread thread2 = new Thread(new BlockIncrementTask(demo));
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
System.out.println("最终计数:" + demo.count);
}
public void blockIncrement() {
// 其他非同步操作...
// 同步块,使用 lock 对象作为锁
synchronized (lock) {
count++;
}
// 其他非同步操作...
}
static class BlockIncrementTask implements Runnable {
private SynchronizedBlockDemo demo;
public BlockIncrementTask(SynchronizedBlockDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 0; i < 10000; i++) {
demo.blockIncrement();
}
}
}
}
同步方法与同步块的比较:
同步方法 | 同步块 |
|---|---|
整个方法都被同步 | 只同步代码块部分 |
锁是 this(非静态方法)或类对象(静态方法) | 锁可以是任意对象 |
可能降低程序效率 | 可以提高程序效率 |
代码简洁 | 更灵活 |
线程协调是指多个线程之间的通信和协作,以完成共同的任务。
在没有适当协调机制的情况下,线程间的协作可能会出现问题。
/**
* 线程协调不当示例
*/
public class IncorrectCoordinationDemo {
private int data = 0;
private boolean dataReady = false;
public static void main(String[] args) {
IncorrectCoordinationDemo demo = new IncorrectCoordinationDemo();
// 生产者线程:生成数据
Thread producer = new Thread(new Producer(demo));
// 消费者线程:处理数据
Thread consumer = new Thread(new Consumer(demo));
consumer.start();
producer.start();
}
static class Producer implements Runnable {
private IncorrectCoordinationDemo demo;
public Producer(IncorrectCoordinationDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 1; i <= 5; i++) {
// 生成数据
demo.data = i;
demo.dataReady = true;
System.out.println("生产者:生成数据 " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer implements Runnable {
private IncorrectCoordinationDemo demo;
public Consumer(IncorrectCoordinationDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 1; i <= 5; i++) {
// 循环等待数据准备好
while (!demo.dataReady) {
// 忙等待,浪费 CPU 资源
}
// 处理数据
System.out.println("消费者:处理数据 " + demo.data);
demo.dataReady = false;
}
}
}
}
上面的代码中,消费者线程使用忙等待的方式等待数据,这会浪费大量的 CPU 资源。
Java 采用监视器(Monitor)模型来实现线程间的协调,主要通过以下方法:
wait():让当前线程进入等待状态,释放对象锁notify():唤醒一个正在等待该对象锁的线程notifyAll():唤醒所有正在等待该对象锁的线程这些方法必须在同步块或同步方法中使用。
/**
* 监视器模型线程协调示例
*/
public class MonitorCoordinationDemo {
private int data = 0;
private boolean dataReady = false;
public static void main(String[] args) {
MonitorCoordinationDemo demo = new MonitorCoordinationDemo();
// 生产者线程:生成数据
Thread producer = new Thread(new Producer(demo));
// 消费者线程:处理数据
Thread consumer = new Thread(new Consumer(demo));
consumer.start();
producer.start();
}
// 生产数据
public synchronized void produce(int value) {
// 等待数据被消费
while (dataReady) {
try {
wait(); // 释放锁,进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生成数据
data = value;
dataReady = true;
System.out.println("生产者:生成数据 " + value);
notify(); // 唤醒等待的消费者线程
}
// 消费数据
public synchronized int consume() {
// 等待数据准备好
while (!dataReady) {
try {
wait(); // 释放锁,进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 处理数据
int value = data;
dataReady = false;
System.out.println("消费者:处理数据 " + value);
notify(); // 唤醒等待的生产者线程
return value;
}
static class Producer implements Runnable {
private MonitorCoordinationDemo demo;
public Producer(MonitorCoordinationDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 1; i <= 5; i++) {
demo.produce(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer implements Runnable {
private MonitorCoordinationDemo demo;
public Consumer(MonitorCoordinationDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 1; i <= 5; i++) {
demo.consume();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
生产者 - 消费者问题是线程协调的经典案例,上面的代码展示了如何使用监视器模型来解决这个问题。
Java 提供了丰富的并发工具类,位于 java.util.concurrent 包中,简化了并发编程的实现。
java.util.concurrent.atomic 包提供了一系列原子变量类,这些类提供了原子操作,避免了使用 synchronized 关键字的开销。
import java.util.concurrent.atomic.AtomicInteger;
/**
* 原子变量示例
*/
public class AtomicVariableDemo {
// 使用原子变量
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 创建两个线程,同时对 count 进行递增操作
Thread thread1 = new Thread(new AtomicIncrementTask());
Thread thread2 = new Thread(new AtomicIncrementTask());
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
System.out.println("最终计数:" + count.get());
}
static class AtomicIncrementTask implements Runnable {
public void run() {
for (int i = 0; i < 10000; i++) {
// 原子递增操作
count.incrementAndGet();
}
}
}
}
常用的原子变量类:
AtomicInteger:原子整数AtomicLong:原子长整数AtomicBoolean:原子布尔值AtomicReference:原子引用Executor 框架提供了一种管理和使用线程的机制,避免了直接创建线程的开销。ExecutorService 是 Executor 的子接口,提供了更多的功能。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* ExecutorService 示例
*/
public class ExecutorServiceDemo {
public static void main(String[] args) throws InterruptedException {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务给线程池
for (int i = 1; i <= 5; i++) {
final int taskId = i;
executor.submit(new Runnable() {
public void run() {
System.out.println("任务 " + taskId + " 正在执行,线程:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务 " + taskId + " 执行完毕");
}
});
}
// 关闭线程池
executor.shutdown();
System.out.println("线程池已关闭");
// 等待所有任务执行完毕
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("所有任务执行完毕");
}
}
常用的线程池:
newFixedThreadPool(n):创建固定大小的线程池newCachedThreadPool():创建可缓存的线程池newSingleThreadExecutor():创建单线程的线程池newScheduledThreadPool(n):创建可定时执行任务的线程池Callable 接口类似于 Runnable,但它可以返回结果,并且可以抛出异常。Future 接口用于获取 Callable 的执行结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Callable 和 Future 示例
*/
public class CallableFutureDemo {
public static void main(String[] args) throws Exception {
// 创建线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交 Callable 任务
Future<Integer> future = executor.submit(new SumTask(1, 100));
System.out.println("任务已提交,等待结果...");
// 可以做其他事情
System.out.println("主线程正在执行其他任务...");
// 获取任务结果,如果任务未完成则阻塞等待
int result = future.get();
System.out.println("1 到 100 的和为:" + result);
// 关闭线程池
executor.shutdown();
}
// 计算从 start 到 end 的和
static class SumTask implements Callable<Integer> {
private int start;
private int end;
public SumTask(int start, int end) {
this.start = start;
this.end = end;
}
public Integer call() throws Exception {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
Thread.sleep(10); // 模拟计算耗时
}
return sum;
}
}
}
java.util.concurrent.locks 包提供了更灵活的锁定机制,Lock 接口是其中的核心。
@startuml
interface Lock {
+ lock()
+ lockInterruptibly() throws InterruptedException
+ tryLock() : boolean
+ tryLock(time: long, unit: TimeUnit) : boolean
+ unlock()
+ newCondition() : Condition
}
class ReentrantLock {
+ ReentrantLock()
+ ReentrantLock(fair: boolean)
+ lock()
+ lockInterruptibly() throws InterruptedException
+ tryLock() : boolean
+ tryLock(time: long, unit: TimeUnit) : boolean
+ unlock()
+ newCondition() : Condition
+ isLocked() : boolean
+ isHeldByCurrentThread() : boolean
+ getHoldCount() : int
}
Lock <|.. ReentrantLock
@enduml
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock 示例
*/
public class LockDemo {
private int count = 0;
// 创建可重入锁
private Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
LockDemo demo = new LockDemo();
// 创建两个线程,同时对 count 进行递增操作
Thread thread1 = new Thread(new LockIncrementTask(demo));
Thread thread2 = new Thread(new LockIncrementTask(demo));
thread1.start();
thread2.start();
// 等待两个线程执行完毕
thread1.join();
thread2.join();
System.out.println("最终计数:" + demo.count);
}
public void lockIncrement() {
// 获取锁
lock.lock();
try {
// 临界区代码
count++;
} finally {
// 确保锁被释放
lock.unlock();
}
}
static class LockIncrementTask implements Runnable {
private LockDemo demo;
public LockIncrementTask(LockDemo demo) {
this.demo = demo;
}
public void run() {
for (int i = 0; i < 10000; i++) {
demo.lockIncrement();
}
}
}
}
Lock 与 synchronized 的比较:
Lock | synchronized |
|---|---|
显式获取和释放锁 | 隐式获取和释放锁 |
可以尝试获取锁,获取不到时可以做其他事情 | 获取不到锁时会阻塞 |
可以响应中断 | 不能响应中断 |
可以实现公平锁 | 非公平锁 |
可以创建多个条件变量 | 只有一个条件变量 |
本章介绍了 Java 并发编程的基础知识,主要包括:
Runnable 接口和继承 Thread 类synchronized 关键字实现方法同步和块同步wait()、notify() 和 notifyAll() 方法ExecutorService、Callable、Future 和 Lock掌握这些基础知识对于编写高效、安全的多线程程序至关重要。在实际开发中,还需要根据具体需求选择合适的并发策略,并注意避免死锁、饥饿等问题。
BlockingQueue 实现一个生产者 - 消费者模型,生产者生成随机数,消费者计算这些数字的平方。
ArrayList 和 CopyOnWriteArrayList 在多线程环境下的性能差异。
希望本章的内容能够帮助你理解 Java 并发编程的基础知识。如果有任何疑问或建议,欢迎在评论区留言讨论!