3128 字
16 分钟

Java多线程编程详解

2026-02-04
浏览量 加载中...

Java多线程编程详解#

什么是线程?#

线程是程序执行的最小单位,一个进程可以包含多个线程。多线程编程可以充分利用CPU资源,提高程序的执行效率。

线程与进程的区别#

  • 进程:是操作系统分配资源的最小单位,每个进程都有自己的内存空间
  • 线程:是程序执行的最小单位,共享进程的内存空间
  • 区别
    • 进程之间相互独立,线程之间共享内存
    • 创建进程的开销比创建线程大
    • 进程切换的开销比线程切换大
    • 一个进程崩溃不会影响其他进程,一个线程崩溃可能会影响整个进程

Java中创建线程的方式#

1. 继承Thread类#

public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("MyThread: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 使用
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}

2. 实现Runnable接口#

public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码
for (int i = 0; i < 10; i++) {
System.out.println("MyRunnable: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 使用
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start(); // 启动线程
}
}

3. 实现Callable接口#

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的代码
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
// 使用
public class CallableDemo {
public static void main(String[] args) {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start(); // 启动线程
try {
// 获取线程执行结果
Integer result = futureTask.get();
System.out.println("Sum: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}

4. 使用线程池#

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Task " + taskId + " is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 关闭线程池
executorService.shutdown();
}
}

线程的生命周期#

Java线程的生命周期包括以下状态:

  1. 新建(New):线程对象被创建,但尚未启动
  2. 就绪(Runnable):线程已经启动,等待CPU分配时间片
  3. 运行(Running):线程正在执行
  4. 阻塞(Blocked):线程等待锁或其他资源
  5. 等待(Waiting):线程等待其他线程的通知
  6. 超时等待(Timed Waiting):线程等待指定时间后自动唤醒
  7. 终止(Terminated):线程执行完毕或异常终止

线程的常用方法#

1. 线程控制方法#

  • start():启动线程
  • run():线程执行的方法
  • sleep(long millis):使线程睡眠指定时间
  • join():等待线程执行完毕
  • interrupt():中断线程
  • isInterrupted():检查线程是否被中断
  • interrupted():检查线程是否被中断,并清除中断状态
  • yield():线程礼让,让出CPU时间片
  • setDaemon(boolean on):设置线程为守护线程
  • isDaemon():检查线程是否为守护线程
  • setPriority(int newPriority):设置线程优先级
  • getPriority():获取线程优先级

2. 线程状态方法#

  • getState():获取线程状态
  • getName():获取线程名称
  • setName(String name):设置线程名称
  • getId():获取线程ID
  • isAlive():检查线程是否存活

线程安全#

什么是线程安全?#

线程安全是指多个线程同时访问共享资源时,不会导致数据不一致或其他意外情况。

线程安全问题的原因#

  • 共享资源:多个线程同时访问同一个资源
  • 非原子操作:操作不是原子的,可能被其他线程中断
  • 重排序:编译器或CPU对指令进行重排序
  • 可见性:一个线程对共享变量的修改,其他线程可能看不到

解决线程安全问题的方法#

1. 同步代码块#

synchronized (lockObject) {
// 线程安全的代码
}

2. 同步方法#

public synchronized void method() {
// 线程安全的代码
}

3. Lock接口#

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
Lock lock = new ReentrantLock();
try {
lock.lock();
// 线程安全的代码
} finally {
lock.unlock();
}

4. 原子类#

import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作

5. 线程安全的集合#

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentHashMap;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

6. 线程局部变量#

import java.util.concurrent.ThreadLocalRandom;
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
threadLocal.set(100);
int value = threadLocal.get();

线程通信#

1. wait()、notify()和notifyAll()方法#

public class ThreadCommunication {
private static final Object lock = new Object();
private static boolean flag = false;
public static void main(String[] args) {
// 生产者线程
new Thread(() -> {
synchronized (lock) {
while (!flag) {
try {
System.out.println("生产者等待...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者生产数据");
flag = false;
lock.notify();
}
}).start();
// 消费者线程
new Thread(() -> {
synchronized (lock) {
System.out.println("消费者消费数据");
flag = true;
lock.notify();
try {
System.out.println("消费者等待...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}

2. CountDownLatch#

import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
final int taskId = i;
new Thread(() -> {
System.out.println("Task " + taskId + " is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " is done");
latch.countDown();
}).start();
}
System.out.println("Waiting for all tasks to complete...");
latch.await();
System.out.println("All tasks are completed");
}
}

3. CyclicBarrier#

import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads are ready");
});
for (int i = 0; i < 3; i++) {
final int threadId = i;
new Thread(() -> {
System.out.println("Thread " + threadId + " is ready");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Thread " + threadId + " continues");
}).start();
}
}
}

4. Semaphore#

import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // 最多允许2个线程同时执行
for (int i = 0; i < 5; i++) {
final int threadId = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread " + threadId + " is running");
Thread.sleep(1000);
System.out.println("Thread " + threadId + " is done");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}).start();
}
}
}

线程池#

什么是线程池?#

线程池是管理线程的容器,它可以重用线程,减少线程创建和销毁的开销。

线程池的优点#

  • 重用线程:减少线程创建和销毁的开销
  • 控制并发数:避免过多线程导致的资源耗尽
  • 管理线程:提供线程的统一管理和监控
  • 提高响应速度:线程已经创建,可立即执行任务

线程池的创建方式#

1. Executors工厂方法#

  • newFixedThreadPool(int nThreads):创建固定大小的线程池
  • newCachedThreadPool():创建可缓存的线程池
  • newSingleThreadExecutor():创建单线程的线程池
  • newScheduledThreadPool(int corePoolSize):创建定时任务的线程池

2. ThreadPoolExecutor构造方法#

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(), // 任务队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

线程池的工作原理#

  1. 当提交任务时,如果核心线程数未满,创建新线程执行任务
  2. 如果核心线程数已满,将任务加入任务队列
  3. 如果任务队列已满,且线程数未达到最大线程数,创建新线程执行任务
  4. 如果线程数已达到最大线程数,执行拒绝策略

线程池的拒绝策略#

  • AbortPolicy:抛出RejectedExecutionException异常
  • CallerRunsPolicy:由调用者线程执行任务
  • DiscardPolicy:丢弃任务
  • DiscardOldestPolicy:丢弃队列中最旧的任务

并发集合#

1. ConcurrentHashMap#

线程安全的HashMap,支持高并发读写。

import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
Integer value = map.get("key");

2. CopyOnWriteArrayList#

线程安全的ArrayList,适用于读多写少的场景。

import java.util.concurrent.CopyOnWriteArrayList;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("element");
String element = list.get(0);

3. ConcurrentLinkedQueue#

线程安全的无界队列,适用于高并发场景。

import java.util.concurrent.ConcurrentLinkedQueue;
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("element");
String element = queue.poll();

4. BlockingQueue#

阻塞队列,支持线程安全的入队和出队操作。

  • ArrayBlockingQueue:基于数组的有界阻塞队列
  • LinkedBlockingQueue:基于链表的可选有界阻塞队列
  • PriorityBlockingQueue:基于优先级的无界阻塞队列
  • DelayQueue:基于延迟时间的无界阻塞队列
  • SynchronousQueue:同步队列,不存储元素

并发工具类#

1. Atomic类#

  • AtomicInteger:原子整数
  • AtomicLong:原子长整数
  • AtomicBoolean:原子布尔值
  • AtomicReference:原子引用
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子递增
count.decrementAndGet(); // 原子递减
count.addAndGet(10); // 原子增加指定值
count.compareAndSet(0, 1); // 原子比较并设置

2. ThreadLocal#

线程局部变量,每个线程都有自己的副本。

import java.util.concurrent.ThreadLocalRandom;
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
threadLocal.set(100);
int value = threadLocal.get();
threadLocal.remove(); // 清理资源,避免内存泄漏

3. CountDownLatch#

倒计时门闩,等待多个线程完成。

4. CyclicBarrier#

循环屏障,等待多个线程到达屏障点。

5. Semaphore#

信号量,控制同时访问资源的线程数。

6. Exchanger#

交换器,两个线程交换数据。

import java.util.concurrent.Exchanger;
Exchanger<String> exchanger = new Exchanger<>();
new Thread(() -> {
try {
String data1 = "Data from thread 1";
System.out.println("Thread 1 sending: " + data1);
String data2 = exchanger.exchange(data1);
System.out.println("Thread 1 received: " + data2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
String data2 = "Data from thread 2";
System.out.println("Thread 2 sending: " + data2);
String data1 = exchanger.exchange(data2);
System.out.println("Thread 2 received: " + data1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

最佳实践#

  1. 优先使用Runnable接口:相比于继承Thread类,实现Runnable接口更灵活,避免了单继承的限制。

  2. 使用线程池:优先使用线程池管理线程,而不是直接创建线程。

  3. 避免使用ThreadLocal的内存泄漏:使用完ThreadLocal后,调用remove()方法清理资源。

  4. 合理使用同步:只同步必要的代码,避免过度同步影响性能。

  5. 使用volatile关键字:对于需要可见性的变量,使用volatile关键字。

  6. 使用原子类:对于简单的计数器等操作,使用原子类而不是同步。

  7. 避免死锁

    • 按顺序获取锁
    • 使用tryLock()尝试获取锁
    • 使用定时锁
  8. 使用并发集合:对于多线程场景,使用线程安全的并发集合。

  9. 设置合理的线程池参数:根据实际需求设置线程池的核心线程数、最大线程数等参数。

  10. 监控线程状态:使用线程池的监控功能,及时发现线程问题。

常见陷阱#

  1. 线程启动方式错误:调用run()方法而不是start()方法,导致线程在主线程中执行。

  2. 死锁:多个线程相互等待对方释放锁,导致程序卡死。

  3. 活锁:线程不断改变自己的状态,导致无法继续执行。

  4. 饥饿:某些线程一直无法获取CPU时间片,导致无法执行。

  5. 内存泄漏:ThreadLocal使用后未清理,导致内存泄漏。

  6. 线程池耗尽:提交过多任务到线程池,导致线程池耗尽。

  7. 过度同步:同步代码块过大,导致性能下降。

  8. 原子性问题:非原子操作导致的数据不一致。

  9. 可见性问题:一个线程对共享变量的修改,其他线程看不到。

  10. 重排序问题:编译器或CPU对指令进行重排序,导致意外结果。

总结#

多线程编程是Java编程中的重要部分,它可以充分利用CPU资源,提高程序的执行效率。但多线程编程也带来了线程安全、线程通信等问题,需要我们谨慎处理。

本文介绍了Java多线程编程的基本概念、创建线程的方式、线程的生命周期、线程安全、线程通信、线程池等内容。希望本文能够帮助你更好地理解和使用Java的多线程编程。

练习#

  1. 编写一个程序,使用继承Thread类的方式创建线程。

  2. 编写一个程序,使用实现Runnable接口的方式创建线程。

  3. 编写一个程序,使用实现Callable接口的方式创建线程,并获取线程执行结果。

  4. 编写一个程序,使用线程池执行多个任务。

  5. 编写一个程序,演示线程的sleep()、join()、yield()方法。

  6. 编写一个程序,演示线程安全问题,并使用同步代码块解决。

  7. 编写一个程序,使用wait()、notify()方法实现线程通信。

  8. 编写一个程序,使用CountDownLatch等待多个线程完成。

  9. 编写一个程序,使用CyclicBarrier实现多个线程的同步。

  10. 编写一个程序,使用Semaphore控制同时访问资源的线程数。

通过这些练习,你将更加熟悉Java的多线程编程,为后续的学习做好准备。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Java多线程编程详解
https://blog.vanilla.net.cn/posts/2026-02-05-java-multithreading/
作者
鹁鸪
发布于
2026-02-04
许可协议
CC BY-NC-SA 4.0

评论区

目录