枫叶の博客
友情链接
往期整理
  •   历史归档
  •   文章分类
  •   文章标签
Maple
文章
5
分类
1
标签
3
友情链接
往期整理
历史归档
文章分类
文章标签
Coding
Java多线程详解
发布于: 2025-1-1
最后更新: 2025-1-4
次查看
Java
type
status
date
slug
summary
tags
category
icon
password
在现代计算机系统中,多线程(Multithreading)是提升程序性能和响应能力的重要手段。Java作为一种支持多线程编程的语言,提供了丰富的API和工具,使开发者能够高效地创建和管理多线程应用程序。本文将详细介绍Java中的多线程概念、实现方式、线程同步、线程通信、并发工具类以及相关的最佳实践,适合作为学习笔记。

1. 多线程的基本概念

1.1 什么是线程和进程?

  • 进程(Process):是操作系统分配资源的基本单位,每个进程拥有独立的内存空间和系统资源。进程之间相互独立,彼此隔离。
  • 线程(Thread):是进程中的执行单元,一个进程可以包含多个线程,线程之间共享进程的内存空间和资源。线程是程序执行的最小单位,拥有自己的栈和程序计数器。

1.2 并发与并行

  • 并发(Concurrency):指在同一时间段内多个任务交替执行,给人一种同时进行的感觉。在单核处理器上,通过快速切换任务实现并发。
  • 并行(Parallelism):指多个任务在同一时刻真正同时执行,通常需要多核处理器的支持。

2. Java 中多线程的实现方式

Java提供了多种方式来实现多线程编程,主要包括继承Thread类、实现Runnable接口、实现Callable与Future接口以及使用Executor框架。

2.1 继承 Thread 类

通过继承java.lang.Thread类并重写其run()方法来创建新线程。
示例:
优点:
  • 简单直观,适合快速创建线程。
缺点:
  • 由于Java是单继承,继承Thread类后无法继承其他类。
  • 不利于资源的共享和复用。

2.2 实现 Runnable 接口

通过实现java.lang.Runnable接口并实现其run()方法,然后将Runnable实例传递给Thread对象。
示例:
优点:
  • 避免了Java的单继承限制,可以同时继承其他类。
  • 更适合多个线程共享同一资源。
缺点:
  • 相比继承Thread类,代码稍微复杂一些。

2.3 实现 Callable 与 Future 接口

Callable接口类似于Runnable,但可以返回结果并抛出异常。与Future接口配合使用,可以获取线程执行的结果。
示例:
优点:
  • 可以获取线程执行的结果。
  • 支持抛出异常。
缺点:
  • 需要使用FutureTask或其他高级并发工具,代码相对复杂。

2.4 使用 Executor 框架

Executor框架提供了更高级的线程管理方式,适用于处理大量线程的情况。主要通过ExecutorService接口及其实现类来管理线程池。
示例:
优点:
  • 提高性能,重用线程,减少资源开销。
  • 提供丰富的线程管理功能,如任务提交、调度、终止等。
  • 易于扩展和维护。
缺点:
  • 需要理解线程池的工作原理和配置。

3. 线程的生命周期

线程在其生命周期中会经历多个状态,Java中的线程状态由java.lang.Thread.State枚举表示:
  1. NEW(新建):线程被创建,但尚未启动。
  1. RUNNABLE(可运行):线程正在运行或等待操作系统分配CPU资源。
  1. BLOCKED(阻塞):线程等待获取一个锁。
  1. WAITING(等待):线程无限期地等待另一个线程来执行特定操作。
  1. TIMED_WAITING(计时等待):线程等待另一个线程来执行特定操作,等待时间有限。
  1. TERMINATED(终止):线程已经完成执行或由于异常退出。
线程状态图示意:

4. 线程的优先级与调度

每个线程都有一个优先级,用于指导线程调度器决定线程执行的顺序。Java线程优先级范围为1到10,Thread.MIN_PRIORITY为1,Thread.MAX_PRIORITY为10,Thread.NORM_PRIORITY为5。
设置线程优先级:
获取线程优先级:
注意事项:
  • 线程优先级只是一个建议,具体的调度行为依赖于操作系统和JVM的实现。
  • 不应过度依赖线程优先级,避免引发不可预测的行为。

5. 线程同步

在多线程环境下,多个线程可能会同时访问共享资源,导致数据不一致或其他问题。线程同步机制用于控制线程对共享资源的访问,确保数据的正确性和一致性。

5.1 synchronized 关键字

synchronized是Java中最基本的同步机制,可以用于方法或代码块,确保同一时间只有一个线程可以执行被synchronized修饰的代码。
同步实例方法:
同步代码块:
优点:
  • 简单易用,内置于Java语言。
  • 支持可重入锁(一个线程可以多次获取同一锁)。
缺点:
  • 锁粒度粗,可能导致性能瓶颈。
  • 容易引发死锁等问题。

5.2 显式锁(Lock 接口)

Java提供了java.util.concurrent.locks.Lock接口及其实现类(如ReentrantLock)作为更灵活的同步机制。
示例:
优点:
  • 更灵活的锁机制,如可中断锁、定时锁等。
  • 可以实现公平锁(线程按申请锁的顺序获取锁)。
缺点:
  • 需要显式获取和释放锁,易出错。
  • 相对于synchronized,代码更复杂。

5.3 原子变量(Atomic 包)

java.util.concurrent.atomic包提供了一系列原子变量类(如AtomicInteger、AtomicLong等),通过无锁编程方式实现线程安全。
示例:
优点:
  • 高性能,避免了锁的开销。
  • 适用于简单的计数等场景。
缺点:
  • 只能用于单一变量的原子操作,复杂操作仍需使用锁。
  • 代码可读性可能较低。

5.4 volatile 关键字

volatile关键字用于声明变量的可见性,确保一个线程对变量的修改对其他线程立即可见。volatile变量不会被线程缓存,每次访问都直接从主内存读取。
示例:
优点:
  • 保证变量的可见性。
  • 适用于状态标志等简单场景。
缺点:
  • 不能保证复合操作的原子性(如i++)。
  • 使用不当可能导致不可预期的行为。

6. 线程间通信

线程间通信用于协调多个线程之间的执行顺序和资源共享。Java提供了多种机制来实现线程间的通信。

6.1 wait、notify 和 notifyAll 方法

这些方法属于java.lang.Object类,用于线程间的等待和通知机制。
  • wait():使当前线程进入等待状态,直到被其他线程唤醒。
  • notify():随机唤醒一个正在等待该对象监视器的线程。
  • notifyAll():唤醒所有正在等待该对象监视器的线程。
示例:生产者-消费者模型
输出示例:
注意事项:
  • wait、notify、notifyAll必须在同步块或同步方法中调用。
  • 使用notifyAll可以避免线程饥饿,但可能导致性能下降。

6.2 Condition 接口

java.util.concurrent.locks.Condition接口提供了更灵活的线程间通信机制,配合显式锁使用。
示例:
优点:
  • 可以创建多个条件变量,提高灵活性。
  • 避免了使用Object的监视器锁带来的局限性。
缺点:
  • 需要显式管理锁和条件,代码更复杂。

7. 死锁及其避免

  • *死锁(Deadlock)**是指两个或多个线程互相等待对方持有的锁,导致所有线程都无法继续执行。
产生死锁的四个必要条件:
  1. 互斥条件:资源被一个线程占用,其他线程无法访问。
  1. 占有且等待:线程持有至少一个资源,并等待获取其他被占用的资源。
  1. 不可剥夺:资源一旦被分配,不能被强制剥夺,只能由持有它的线程释放。
  1. 循环等待:形成一个等待资源的环路。
示例:
避免死锁的方法:
  1. 资源有序分配:为所有资源定义一个全局的顺序,线程按顺序请求资源,避免循环等待。
  1. 避免占有且等待:线程在请求资源前,释放已持有的资源。
  1. 使用超时机制:在获取锁时设置超时时间,超时则释放资源,避免永久等待。
  1. 检测和恢复:定期检测死锁并采取措施,如终止某些线程。
示例:资源有序分配

8. 并发工具类

Java的java.util.concurrent包提供了丰富的并发工具类,简化了多线程编程,提升了开发效率。

8.1 CountDownLatch

CountDownLatch用于让一个或多个线程等待,直到其他线程完成各自的任务。
示例:
输出示例:

8.2 CyclicBarrier

CyclicBarrier允许一组线程互相等待,直到所有线程都达到某个屏障点,然后继续执行。与CountDownLatch不同,CyclicBarrier可以循环使用。
示例:
输出示例:

8.3 Semaphore

Semaphore用于控制同时访问某个特定资源的线程数量。可以用于限流、资源池等场景。
示例:
输出示例:

8.4 Exchanger

Exchanger用于在两个线程之间交换数据,适用于需要对等交换信息的场景。
示例:
输出示例:

8.5 Phaser

Phaser是一个更灵活的同步屏障,可以动态地增加和减少参与的线程。
示例:
输出示例:

9. Java 8 及以后版本的并发新特性

Java 8引入了许多新的并发特性和工具,进一步简化了多线程编程。

9.1 CompletableFuture

CompletableFuture是Java 8引入的一个强大的异步编程工具,支持链式调用、组合、异常处理等。
示例:
优点:
  • 支持异步编程,非阻塞执行。
  • 丰富的组合操作,如thenApply、thenCombine、thenAccept等。
  • 内置的异常处理机制。
缺点:
  • 需要理解异步编程模型,学习曲线较陡。

9.2 并行流(Parallel Streams)

Java 8的Streams API支持并行操作,通过并行流(parallelStream)可以轻松实现数据的并行处理。
示例:
优点:
  • 简化并行操作的实现。
  • 自动利用多核处理器,提高性能。
缺点:
  • 并行流的性能提升依赖于具体任务和硬件环境。
  • 可能引发线程安全问题,需谨慎使用。

10. 线程安全的集合类

Java提供了多种线程安全的集合类,确保在多线程环境下的数据一致性和安全性。

10.1 同步集合

通过Collections.synchronizedXXX方法将非线程安全的集合包装为线程安全的集合。
示例:
优点:
  • 简单易用,适用于现有集合的同步需求。
缺点:
  • 整个集合被同步,锁粒度粗,可能导致性能瓶颈。
  • 在迭代时需要手动同步,容易出错。

10.2 并发集合

java.util.concurrent包提供了一系列高性能的并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等。
常用的并发集合:
  • ConcurrentHashMap:线程安全的哈希映射,支持高并发的读写操作。
  • CopyOnWriteArrayList:适用于读多写少的场景,写操作会复制底层数组。
  • ConcurrentLinkedQueue:无界的线程安全队列,基于链接节点实现。
  • BlockingQueue及其实现类:支持阻塞操作的队列,适用于生产者-消费者模型。
示例:ConcurrentHashMap
优点:
  • 高性能,适合高并发场景。
  • 内部采用细粒度锁或无锁算法,减少锁竞争。
缺点:
  • 复杂的内部实现,理解起来较为困难。
  • 某些操作(如迭代)可能反映的是弱一致性的视图。

11. 多线程的最佳实践

为了编写高效、健壮和可维护的多线程程序,遵循以下最佳实践是非常重要的。

11.1 优先使用高层次的并发工具

尽量使用java.util.concurrent包提供的并发工具类,如ExecutorService、CountDownLatch、Semaphore等,避免手动管理线程和锁。

11.2 避免使用共享可变状态

共享可变状态容易引发数据竞争和同步问题。尽量设计无状态或使用不可变对象,减少共享和同步的需求。

11.3 尽量减少同步的范围

只在必要的代码段使用同步,避免锁的持有时间过长,减少锁竞争和性能瓶颈。

11.4 使用合适的锁机制

根据具体需求选择适当的锁机制,如ReentrantLock提供的高级功能,或ReadWriteLock实现读写分离。

11.5 避免死锁

遵循资源有序分配、避免占有且等待等原则,设计线程间的交互,避免产生死锁。

11.6 使用线程池管理线程

使用ExecutorService管理线程池,避免频繁创建和销毁线程,提升性能和资源利用率。

11.7 适当处理异常

在线程中捕获并处理异常,避免线程意外终止导致资源泄露或程序不稳定。
示例:

11.8 使用不可变对象

不可变对象天然线程安全,减少同步的需求,提高代码的可靠性和可维护性。
示例:

11.9 避免过度使用线程

线程的创建和上下文切换是有开销的,避免过度创建线程,合理配置线程池的大小,提升系统的整体性能。

12. 示例代码

以下示例综合展示了多线程编程的各个方面,包括线程创建、同步、线程间通信、并发工具类等。
输出示例:
解释:
  1. ExecutorService 管理线程:使用线程池提交任务,并通过Future获取任务结果。
  1. 线程同步:通过显式锁和原子变量保证计数器的线程安全。
  1. 线程间通信(生产者-消费者):使用BlockingQueue实现生产者放入和消费者取出数据的同步。
  1. CountDownLatch:等待多个任务完成后,主线程继续执行。
  1. CompletableFuture:异步执行任务,并在完成后处理结果。

13. 总结

多线程编程是Java中的重要特性,能够显著提升应用程序的性能和响应能力。然而,多线程编程也带来了复杂性,如线程同步、死锁、竞态条件等问题。通过合理使用Java提供的多线程工具和并发框架,遵循最佳实践,可以有效地管理多线程环境,编写高效、可靠的并发程序。
关键要点:
  • 理解线程的基本概念和生命周期。
  • 掌握不同的线程创建和管理方式,如继承Thread类、实现Runnable和Callable接口、使用Executor框架等。
  • 学习线程同步机制,包括synchronized、显式锁、原子变量和volatile关键字。
  • 掌握线程间通信的方法,如wait/notify、Condition接口。
  • 了解并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等。
  • 避免常见的并发问题,如死锁、线程安全问题。
  • 利用Java 8及以后版本的新特性,提升并发编程的效率和简洁性。
通过持续的实践和学习,可以深入理解和掌握Java中的多线程编程,为开发高性能、高可用的应用程序奠定坚实的基础。
  • 作者:Maple
  • 链接:https://mapleleaf.space/Coding/Java/Multi-Thread
  • 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章
Java 集合框架详解
Java概述
Java 集合框架详解Java概述
Loading...
目录
0%
1. 多线程的基本概念1.1 什么是线程和进程?1.2 并发与并行2. Java 中多线程的实现方式2.1 继承 Thread 类2.2 实现 Runnable 接口2.3 实现 Callable 与 Future 接口2.4 使用 Executor 框架3. 线程的生命周期4. 线程的优先级与调度5. 线程同步5.1 synchronized 关键字5.2 显式锁(Lock 接口)5.3 原子变量(Atomic 包)5.4 volatile 关键字6. 线程间通信6.1 wait、notify 和 notifyAll 方法6.2 Condition 接口7. 死锁及其避免8. 并发工具类8.1 CountDownLatch8.2 CyclicBarrier8.3 Semaphore8.4 Exchanger8.5 Phaser9. Java 8 及以后版本的并发新特性9.1 CompletableFuture9.2 并行流(Parallel Streams)10. 线程安全的集合类10.1 同步集合10.2 并发集合11. 多线程的最佳实践11.1 优先使用高层次的并发工具11.2 避免使用共享可变状态11.3 尽量减少同步的范围11.4 使用合适的锁机制11.5 避免死锁11.6 使用线程池管理线程11.7 适当处理异常11.8 使用不可变对象11.9 避免过度使用线程12. 示例代码13. 总结
Maple
Maple
你也想做游戏嘛?
文章
5
分类
1
标签
3
最新发布
Java概述
Java概述
2025-1-4
ChatBox调用API
ChatBox调用API
2025-1-4
DSCode
DSCode
2025-1-4
Java多线程详解
Java多线程详解
2025-1-4
Java 集合框架详解
Java 集合框架详解
2025-1-4
公告
-- 学无止境 ---
目录
0%
1. 多线程的基本概念1.1 什么是线程和进程?1.2 并发与并行2. Java 中多线程的实现方式2.1 继承 Thread 类2.2 实现 Runnable 接口2.3 实现 Callable 与 Future 接口2.4 使用 Executor 框架3. 线程的生命周期4. 线程的优先级与调度5. 线程同步5.1 synchronized 关键字5.2 显式锁(Lock 接口)5.3 原子变量(Atomic 包)5.4 volatile 关键字6. 线程间通信6.1 wait、notify 和 notifyAll 方法6.2 Condition 接口7. 死锁及其避免8. 并发工具类8.1 CountDownLatch8.2 CyclicBarrier8.3 Semaphore8.4 Exchanger8.5 Phaser9. Java 8 及以后版本的并发新特性9.1 CompletableFuture9.2 并行流(Parallel Streams)10. 线程安全的集合类10.1 同步集合10.2 并发集合11. 多线程的最佳实践11.1 优先使用高层次的并发工具11.2 避免使用共享可变状态11.3 尽量减少同步的范围11.4 使用合适的锁机制11.5 避免死锁11.6 使用线程池管理线程11.7 适当处理异常11.8 使用不可变对象11.9 避免过度使用线程12. 示例代码13. 总结
2024.11.10-2025Maple.

枫叶の博客 | 你也想做游戏嘛?

Powered byNotionNext 4.7.7.