disgare 的博客
首页
博客
分类
标签
首页
博客
分类
标签
  • 网络

    • 计算机网络学习笔记
    • 网络安全相关
    • 域名和子网掩码
    • CORS 跨域资源共享
    • DNS、HTTP 与 HTTPS
    • Server-Sent Events (SSE)
    • WebSocket 长连接
  • 计算机基础

    • 操作系统 IO 相关知识
    • 操作系统学习笔记
    • 程序的机器级表示
    • 音频文件基础
    • 正则表达式相关概念
    • ffmpeg 的安装以及实现音频切分功能
    • Hex 和 Base64 编码
    • XML 的使用
  • 数据结构与算法

    • 动态规划算法学习笔记
    • 基于比较的排序算法的最坏情况下的最优下界为什么是O(nlogn)
    • 集合与数据结构学习笔记
    • 面试常见算法总结
    • 算法导论第二部分排序学习笔记
    • 算法导论第一部分学习笔记
  • Java

    • 对象之间的映射与转换
    • 反射学习笔记
    • 泛型相关概念
    • 关于 boolean 类型的坑
    • 如何使用 lambda 表达式实现排序
    • CompletableFuture 相关用法
    • CompletableFuture 源码浅要阅读
    • FutureTask 源码阅读
    • Guava 常用 API
    • Guava 源码阅读:Multimap 相关
    • Jackson 的各种使用
    • Java 的 Excel 相关操作
    • java 的常见性能问题分析以及出现场景
    • java 基础知识
    • JAVA 枚举的基础和原理
    • Java 图片文件上传下载处理
    • Java 序列化
    • Java 异常
    • Java 语法糖
    • Java 中关于字符串处理的常用方法
    • Java 中强、软、弱、虚引用
    • JAVA 注解小结
    • Java Http 访问框架
    • Java Stream 的使用
    • Java8 新特性
    • netty 学习笔记
    • Scanner 的各种用法
    • Servlet 学习笔记
    • String、StringBuffer、StringBuilder 学习笔记
  • JVM

    • 虚拟机执行子系统
    • JVM 自动内存管理
    • Linux 中 JVM 常用工具以及常见问题解决思路
  • Linux

    • crontab 表达式
    • Linux 常见命令
    • Linux 文件系统
  • 中间件

    • 关于定时任务原理
    • 详解 kafka
    • ES 搜索引擎
    • flink 提交流程
    • Grape-RAG
    • Hadoop 基础原理
  • 多线程

    • 多线程基础学习笔记
      • 了解多线程
        • 进程与线程
        • 并发与并行
        • java 线程的实现
        • 管程 Monitor
        • 协程
        • 为什么要使用多线程
        • 那如何避免和预防死锁
        • 线程安全的分类
      • 多线程实现方式
        • 继承 Thread 类
        • 为什么不直接使用 run 方法
        • 实现 Runnable 接口
        • Runnable 接口为什么可以这么实现
        • 实现 Callable 接口
        • FutureTask 和 Future 接口
        • 同步调用、回调、异步调用(异步回调)
      • 线程的生命周期
      • 线程调度以及调度线程的方法
        • 优先级
        • 让步
        • 休眠
        • 插队(join)
        • 中断(interrupt)
        • 守护线程
      • 线程间通信方式
    • 简单了解并发集合
    • 如何手写单例
    • 深入理解 java 多线程安全
    • 生产者消费者问题
    • 线程池作用、用法以及原理
    • AQS 组件
    • ThreadLocal 原理以及使用
  • 非关系型数据库

    • Redis 集群
    • Redis 数据结构、对象与数据库
    • Redis 学习笔记
  • 关系型数据库

    • B+ 树的插入、删除和数据页分裂机制
    • MySQL 的 binglog、redolog、undolog
    • MySQL 的记录存储结构、存储引擎与 Buffer Pool
    • MySQL 基本的特性
    • MySQL 开发规范
    • MySQL 事务与锁与 MVCC
    • MySQL 数据类型、字符集相关内容
    • MySQL 索引与索引优化
    • PostgreSQL 更新数据时 HOT优化
    • PostgreSQL 相关用法
  • Python

    • Python 基础语法
    • Python 学习
  • Spring 项目

    • Lombok 的常用注解
    • maven 小结
    • MyBatis 框架的使用
    • MyBatis 重要知识点总结
    • MybatisPlus 的使用
    • Spring 框架基础使用
    • Spring 事务相关
    • Spring IOC 的原理及源码
    • Spring AOP 的使用和原理
    • SpringBoot 的原理
    • SpringBoot 基础使用
    • SpringWeb 重要知识点
  • 分布式

    • 初步了解 docker
    • 从 ACID 到 BASE 事务处理的实现
    • 访问远程服务
    • 分布式 id
    • 分布式缓存相关问题
    • 分布式集群理论和分布式事务协议
    • 分布式架构的观测
    • 分布式一致性算法
    • 负载均衡 Load Balancing
    • 关于分布式系统 RPC 中高可用功能的实现
    • 集群间数据同步的目的
    • 三高问题下的系统优化
    • 数据库分库分表
    • 详解 Spring Cloud
    • Dubbo 基础概念
    • Gossip 协议
    • nginx 学习笔记
    • Protobuf 通信协议
    • Zookeeper 基础学习
  • 架构设计

    • 参数校验与异常处理
    • 抽象方法与设计模式
    • 代码整洁之道
    • 权限系统设计
    • 用低内存处理大量数据
    • 设计模式——策略模式
    • 设计模式——过滤器模式在 Spring 中的实践
    • 状态模式
    • 统一结果返回
    • 为什么要打日志?怎么打日志?打什么日志?
    • 运维监控常见指标含义
    • 资深研发进阶
    • DDD 架构学习笔记
    • Java 常用的规则引擎
    • MVC 架构学习笔记
  • AI

    • 如何编写 Prompt
    • Agent 工程架构
    • LLM 相关内容
    • NLP 相关知识
    • vibe coding 最佳实践
    • windows 下 ollama 迁移到 D 盘
  • 开发工具

    • 如何画时序图、流程图、状态流转图
    • excel 关于 =vlookup 的用法
    • git 的学习以及使用
    • IDEA 插件推荐
    • IDEA 常用快捷键以及调试
    • Shell 脚本
    • swagger 的使用
  • 前端

    • 简单了解前端页面开发
    • 伪静态是什么
    • GitHub Pages 部署教程
    • Vercel 部署教程
    • vue-admin-template 简单使用
    • VuePress 博客搭建指南
  • 项目

    • 面试刷题网——技术方案
    • 影视资源聚合站——技术方案
  • 问题记录

    • 定时任务单线程消费 redis 中数据导致消费能力不足
    • 提供可传递的易受攻击的依赖项
    • Liteflow 在 SpringBoot 启动时无法注入组件问题 couldn‘t find chain with the id[THEN(NodeComponent)]
  • 金融

    • 股票分析——关于电力
    • 股票技术面——量价关系
    • 股票技术面——盘口
    • 股票技术面——基础
    • 基础的金融知识
    • 基金与股票
    • 韭菜的自我总结
    • 聊聊价值投资
  • 其他

    • 程序员职场工作需要注意什么
    • 创业全链路SOP:从灵光一现到系统化增长的实战指南
    • 观罗翔讲刑法随笔
    • 价格和价值
    • 立直麻将牌效益理论
    • 梅花易数学习笔记
    • 压力管理
2021-03-05
多线程
目录

多线程基础学习笔记

# 了解多线程

# 进程与线程

进程是一个电脑上的程序,线程是进程中的执行单位

进程在 java 虚拟机上的实现是拥有许多线程与堆、方法区,线程拥有自己的栈、程序计数器、本地方法栈。即一个进程有多个线程可以实现自己的独立调度,并且这些线程又有共同的资源区域

# 并发与并行

并发:并发是指多个任务在同一时间段内交替执行。这些任务可能在不同的时间点开始和结束,但它们共享同一个处理器资源。操作系统通过调度机制在多个任务之间快速切换,使得每个任务看起来像是同时在运行

并行:并行是指多个任务同时在多个处理器或核心上执行。每个任务都有独立的处理器资源,因此可以真正地同时进行

# java 线程的实现

我们知道操作系统提供的线程分配资源需要实现才可以使用

1,内核线程实现:内核线程是由操作系统内核直接管理和调度的线程,与用户线程相比,内核线程具有更高的优先级和更强的并发能力。在 Java 中,内核线程的创建和管理是由操作系统和 Java 虚拟机共同完成的,开发者无法直接控制内核线程的创建和调度

2,用户线程实现:就是让用户来实现线程的调用规则,操作系统只负责分配资源给进程,线程的创建、销毁、同步都是由用户来决定的。用户线程的创建和调度由用户程序控制,因此需要确保在主线程退出之前,所有的用户线程都已经执行完毕或被显式地停止

而 HotSpot 的线程实现是内核线程实现,每一个线程都是直接映射到操作系统原生线程来实现的。堆可以对应内存,本地方法栈与虚拟机栈可以对应寄存器与处理器。此外,还有一些虚拟机会使用其他的实现方式

# 管程 Monitor

Monitor 直译是监视器,它并非线程,而是一种并发编程的同步机制,用于协调多个线程之间的访问和操作共享资源。它提供了一种结构化的方式来管理线程的互斥访问和条件等待,它的最终作用是让线程安全

管程指的是管理共享变量以及对共享变量的操作过程,让它们支持并发。翻译为 Java 就是管理类的成员变量和成员方法,让这个类是线程安全的

在任何时候,只有一个线程可以进入管程中的某个方法,从而避免多个线程同时访问共享数据导致的竞态条件,当一个线程试图调用管程中的某个方法时,它必须先获取管程的锁

# 协程

协程的本质是一个用户状态下的单个线程通过时分复用分出来的,说人话就是它可以在执行过程中多次暂停,并在需要时恢复执行,它的调度和切换是由程序员(用户进程)显式控制的,而非依赖操作系统的抢占式调度,它可以看作是一种更高级的函数

不管是进程还是线程,每次阻塞、切换都需要陷入系统调用,先让 CPU 跑操作系统的调度程序,然后再由调度程序决定该跑哪一个线程。而且由于抢占式调度(抢占式线程)执行顺序无法确定的特点,使用线程时需要非常小心地处理同步问题。为了避免阻塞时使用系统调用,也避免用户切换状态时的开销过大,我们写出了协程(协作式线程)

协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置,协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多

简单点来说就是协程中函数或者一段程序能够被挂起(说暂停其实也没啥问题),待会儿再恢复

有栈协程指每个协程会保存单独的上下文(执行栈、寄存器等),协程的唤醒和挂起就是拷贝、切换上下文;无栈协程指单个线程内所有协程都共享同一个执行栈,协程的切换就是简单的函数返回

截至目前(2025年2月27日),Java 在标准库和核心语言层面通过 Project Loom 引入了对协程的支持,具体实现为虚拟线程(Virtual Threads)。虚拟线程由 JVM 管理,而不是直接映射到操作系统线程。一个操作系统线程可以承载成千上万的虚拟线程,极大提高了并发能力。切换由 JVM 调度器控制,通常在阻塞操作(如 I/O)时自动挂起和恢复,开发者无需显式调用 yield

# 为什么要使用多线程

单处理器:io 操作与 cpu 操作不能同时运行,多线程可以提高程序运行效率 多处理器:使用单线程无法同时利用所有 cpu 服务器:需要同时响应多个用户请求

# 那如何避免和预防死锁

编写并发代码的时候需要考虑2个问题

1,线程安全 2,死锁

处理死锁一共有四种方法,又分为预防死锁的方法和避免死锁的方法

预防死锁的方法(也是解决哲学家进餐问题的方法):

1,一次申请所有要使用的资源 2,申请不到资源时,可以放弃已有资源 3,让资源按序获取

避免死锁的方法:我们启动一个预处理线程,在每次分配资源之前,计算这个资源分配给这个线程会不会出现死锁的情况

# 线程安全的分类

什么是线程安全?就是多个线程在操作同一个共享对象的时候可能会发生一些不正确的结果,对共享变量的修改是产生线程安全问题的重点,而有些操作就算访问了共享变量也不会造成任何线程安全问题,java 对共享变量的访问方式一共有以下几类:

1,不可变:被 final 修饰的变量,只能进行读取,不能进行更新,自然是线程安全的 2,绝对线程安全:不管运行时环境如何,调用者都不需要任何的额外同步操作,达成这个条件是非常困难的 3,相对线程安全:通常意义上的线程安全,java 中提供的大部分线程安全的类都是相对线程安全,通过一些同步操作配合这些类实现线程安全,比如 hashtable、vector 等 4,线程兼容:可以使用同步手段保证在并发环境正常使用,比如 hashmap 5,线程对立:不管采用了什么线程安全措施都无法保证线程安全,这在 java 中很少出现

# 多线程实现方式

# 继承 Thread 类

1,构造 Thread 子类,重写 run 方法 2,创建该子类实例对象,调用 start 方法 特点:java 只能继承一个父类

# 为什么不直接使用 run 方法

执行 run 只能运行里面的代码,start 才能启动一个线程

# 实现 Runnable 接口

1,构造 Runnable 接口子类实例对象,重写 run 方法 2,创建该子类实例对象 3,调用有参的 Thread 构造方法,调用 start 方法 特点:java 可以实现多个接口,避免单继承的局限性 更好的处理共享资源的情况

# Runnable 接口为什么可以这么实现

先来看看 Thread 的 run 方法

    private Runnable target;

    public void run() {
        if (this.target != null) {
            this.target.run();
        }

    }
1
2
3
4
5
6
7
8

该方法意思是,先判断类里的 Runnable 是否为空,否则执行 target 的run方法,而创建该子类实例对象这一步就将自己创建的Runnable传入了Thread中

# 实现 Callable 接口

public interface Callable<V> {
    V call() throws Exception; // 返回泛型结果 V
}
1
2
3

1,构造 Callable 接口子类实例对象,重写 call 方法 2,创建该子类实例对象 3,调用有参的 FutureTask 构造方法 4,调用有参的 Thread 构造方法,调用 start 方法

特点:有返回值,运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果,可声明抛出异常

代码实例:

public class Test2 {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Thread1 t1 = new Thread1();
		t1.start();
		Thread2 t2 = new Thread2();
		new Thread(t2).start();
		Thread3 t3 = new Thread3();
		FutureTask<Object> f = new FutureTask<>(t3);
		Thread t = new Thread(f);
		t.start();
		System.out.println(f.get());
	}
}

class Thread1 extends Thread{
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
	}
}

class Thread2 implements Runnable{
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
	}
}

class Thread3 implements Callable<Object>{
	public Object call() throws Exception {
		// TODO Auto-generated method stub
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
		return 10;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# FutureTask 和 Future 接口

Future 接口主要是对并发任务的执行及获取其结果的一些操作。主要有三大功能:判断并发任务是否执行完,获取并发的任务完成后的结果,取消并发执行的任务

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws ...;
}
1
2
3
4
5
6
7

当计算完成后,只能通过 get() 方法得到结果,get 方法会阻塞直到结果准备好了。如果想取消,那么调用 cancel() 方法。其他方法用于确定任务是正常完成还是取消了 在这里插入图片描述

而 FutureTask 内部自己实现了默认的 Callable 接口,但是可以传入一个新的 Callable 覆盖,FutureTask 是一个提供异步计算的结果的任务,它同时实现了 Future 和 Runnable 接口,因此它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值

FutureTask 类的 get 方法,只进一次线程调用,此后直接返回以前的值;异步获取结果的同时,主线程是阻塞的。所以可以将其归为异步阻塞模式

FutureTask 底层原理为内部通过状态机(volatile int state)跟踪任务状态(NEW、COMPLETING、NORMAL、EXCEPTIONAL等),结果存储通过 outcome 字段(volatile Object)保证可见性,调用 get() 时,若任务未完成,线程会通过 LockSupport.park() 进入等待。

# 同步调用、回调、异步调用(异步回调)

同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用,像Future的get方法就是同步调用

回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口,具体做法可以是函数式接口实现

异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调常常是异步调用的基础

CompletableFuture可以实现异步回调

# 线程的生命周期

线程的生命周期描述了一个线程从创建到终止的整个过程,包括多个不同的状态。在 Java 中,线程的生命周期可以分为以下几个阶段。以下出自《Java 并发编程艺术》

  • 新建:对象创建时,未调用 start 方法。未分配系统资源时
  • 可执行:可运行线程的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统(如处理器)的其他资源,因此我们可以切割为操作系统中以下两个状态(只有6个状态是因为 java 源码中 Thread.State 切分了6个状态) -- 就绪:调用 start 方法后变为就绪状态,此时线程已经分配了系统资源,并且可以被调度执行。然而,线程还没有开始执行,只是等待系统调度 -- 运行:当线程被系统调度并开始执行时,线程进入运行状态。此时线程正在执行其任务代码
  • 阻塞:满足以下两种条件之一都会出现阻塞:等待获取锁时发生阻塞,发出 IO 请求时也会发生阻塞,总之阻塞是被动的行为。在阻塞状态下,线程暂停执行,不会占用 CPU 资源
  • 无期等待:使用无参的 join、wait 等方法会发生等待,等待是主动行为,会释放锁和 CPU 资源,其他的线程使用 notify、notifyAll 方法后该线程进入就绪状态
  • 限期等待:使用有参的 sleep、wait、join 会定时等待,时间到也会进入就绪,会释放占有的 CPU 资源,只有 sleep 不会释放锁,其他都会释放锁
  • 死亡:执行完毕,或者抛出异常,错误时线程死亡。注意,死亡后无法进入其他状态,线程是不可能复活的

关于 join、wait、sleep 在这里插入图片描述

请注意不要混淆操作系统线程状态和 java 线程状态。JVM 中的线程必须只能是以上6种状态的一种。操作系统的状态是以下五个 在这里插入图片描述

# 线程调度以及调度线程的方法

强占调度:按优先级争取 CPU 资源 定时调度:线程获得定时大小的时间片并执行

# 优先级

高优先级容易抢到CPU资源 优先级分为1到10 优先级改变使用 setPriority 方法

# 让步

线程进入就绪状态,放弃 cpu 资源 使用 yield 方法

# 休眠

使用 sleep 方法使线程休眠

# 插队(join)

现在有一个任务:在所有子线程执行完毕之后再执行主线程,该怎么做?

答案是使用 join 方法,该方法让调用这个方法的线程阻塞直到被调用的线程执行完毕。比如 A 线程插入 B 线程中,只有 A 运行结束才能运行 B

# 中断(interrupt)

Java 没有提供任何机制来安全地终止线程,但提供了中断机制,即 thread.interrupt() 方法,该方法主要作用就是让线程中断

线程中断是一种协作式的机制,并不是说调用了中断方法之后目标线程一定会立即中断,而是发送了一个中断请求给目标线程,目标线程会自行在某个取消点中断自己。它的作用其实是通知该线程应该被中断了

这种设定很有必要,为什么不能从外部强制性的终止一个线程呢?因为如果不论线程执行到何种情况都立即响应中断的话,很容易造成某些对象状态不一致的情况出现,线程应该有机会完成它正在执行的任务,并进行必要的清理工作

同时,由于需要该线程自己中断自己,它需要经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程

源码如下:

    private volatile Interruptible blocker;
    private final Object blockerLock = new Object();
   // 核心 interrupt 方法
   public void interrupt() {
   		// 先检查非本线程是否有权限
        if (this != Thread.currentThread()) 
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // 仅仅设置interrupt标志位
                b.interrupt(this);    // 调用如 I/O 操作定义的中断方法
                return;
            }
        }
        interrupt0();
    }
    // 静态方法,调用该方法调用后会清除中断状态。
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    // 这个方法不会清除中断状态
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 守护线程

守护线程(Daemon Thread)是一种在后台运行的线程,它的存在并不会阻止程序的终止。与之相对的是用户线程(User Thread),用户线程是程序的主要执行线程,当所有的用户线程结束时,程序才会终止

守护线程有以下几个主要的用途:

  • 后台任务:守护线程通常用于执行一些后台任务,这些任务不需要与用户交互或等待用户输入,而是在后台默默地执行。例如,垃圾回收器就是一个守护线程,负责回收不再使用的内存
  • 资源管理:守护线程可以用于管理和监控一些资源,例如数据库连接池、网络连接池等。它们可以周期性地检查资源的状态,并进行相应的管理和维护

需要注意的是,守护线程在程序终止时会被强制终止,因此不能依赖于守护线程来执行关键性的任务或保证数据的完整性。它们主要用于辅助和支持用户线程的工作,提供一些额外的功能和服务

在 Java 中,可以通过 Thread 类的 setDaemon(true) 方法将一个线程设置为守护线程。默认情况下,线程是用户线程。需要注意的是,setDaemon() 方法必须在调用 start() 方法之前设置,否则会抛出 IllegalThreadStateException 异常

# 线程间通信方式

1,volatile、synchronized 关键字 + 等待通知机制

关键字 volatile 可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性,从而间接实现线程之间数据的通信

关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性

可以通过 Java 内置的等待通知机制实现一个线程修改一个对象的值,而另一个线程感知到了变化,然后进行相应的操作

这种方式用来实现比较麻烦的线程间通信,比如生产者消费者问题

2,管道输入/输出流

管道输入、输出流和普通的文件输入输出流或者网络输入输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存

管道输入输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、 PipedReader和PipedWriter,前两种面向字节,而后两种面向字符

3,使用 Thread.join()

如果一个线程 A 执行了 thread.join 语句,其含义是当前线程 A 等待 thread 线程运行结束之后再继续往下运行,这意味着 thread 线程执行修改的数据是肯定可以被线程 A 读取到的

4,可重入锁 + condition

#线程#并发
最后更新: 2/23/2026, 9:23:04 AM
Hadoop 基础原理
简单了解并发集合

← Hadoop 基础原理 简单了解并发集合→

最近更新
01
vibe coding 最佳实践
02-24
02
立直麻将牌效益理论
02-23
03
伪静态是什么
02-08
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式