06-并发

作者:前端兰博日期:2026/9/16

一、线程创建

Java中常见创建线程有三种,常见的如下两类

1.1 继承Threa类

1public class MyThread extends Thread{
2    @Override
3    public void run(){
4        System.out.println("MyThread is running");
5    }
6}
7
8public static void main(String[] args) {
9    MyThread myThread = new MyThread();
10    myThread.start();
11}
12

1.2 实现Runnable接口

由于继承Thread类后无法继承其它类,因此实现Runnable接口是常见的线程创建方式。Runnable接口只有1个抽象方法run,因此可以直接使用lambda表达式。

1public static void main(String[] args) {
2    Thread t1 = new Thread(()->{
3    System.out.println("Thread 1 is running");
4   });
5   t1.start();
6}
7

二、常用方法

2.1 sleep

sleep是让当前执行的线程让出cpu占用权n毫秒

1public static void main(String[] args) throws InterruptedException {
2   Thread t1 = new Thread(()->{
3        System.out.println("Thread 1 is running");
4        try {
5            Thread.sleep(1000);
6        } catch (InterruptedException e) {
7            e.printStackTrace();
8        }
9        System.out.println("Thread 1 is ending");
10   });
11
12   t1.start();
13   try {
14    Thread.sleep(3000);
15   } catch (InterruptedException e) {
16    e.printStackTrace();
17   }
18   System.out.println("main thread is running");
19}
20
  1. main线程先执行,走到了t1线程,t1和main同时抢占cpu,然后main被休眠了3s。
  2. t1线程执行thread1 running,此时t1自己线程也被休眠1s,此时main线程和t1都在休眠。
  3. t1后,抢占cpu,输出Thread 1 is ending
  4. main最后才醒来,抢到cpu,输出main thread is running

2.2 join

join让当前线程暂停直到目标线程结束后。如下代码,如果没有t1.join方法,最后的输出肯定是Thread 1 is ending。但t1.join让main线程暂停,必须等t1线程执行结束才能继续,所以输出是

  1. Thread 1 is running
  2. Thread 1 is ending
  3. main thread is running
1public static void main(String[] args) throws InterruptedException {
2   Thread t1 = new Thread(()->{
3        System.out.println("Thread 1 is running");
4        try {
5            Thread.sleep(1000);
6        } catch (InterruptedException e) {
7            e.printStackTrace();
8        }
9        System.out.println("Thread 1 is ending");
10   });
11   t1.start();
12   t1.join();
13   System.out.println("main thread is running");
14}
15
16

2.3 interrupt

interrupt给目标线程贴请停标签;如果对方阻塞会立刻醒,其它情况需要isInterrupted方法判断是否有停止标签。

休眠被唤醒

1public static void main(String[] args) throws InterruptedException {
2   Thread t1 = new Thread(()->{
3        System.out.println("Thread 1 is running");
4        try {
5            Thread.sleep(5000);
6        } catch (InterruptedException e) {
7            System.out.println("Thread 1 is interrupted");
8        }
9        System.out.println("Thread 1 is ending");
10   });
11
12   t1.start();
13   t1.interrupt();
14   System.out.println("main thread is running");
15}
16
  1. t1线程在执行时,interrupt告诉t1你需要暂停下,此时t1如果刚好在sleep休眠时,立马被唤醒走到catch,输出Thread 1 is interrupted。
  2. t1与main线程继续互相抢占cpu。也就是Thread 1 is ending与main thread is running输出顺序随机。

其它情况

线程被interrupt告知暂停时,如果线程刚好休眠会被唤醒进入catch。但是如果线程是其它状态,必须使用isInterrupted获取状态来处理

1public static void main(String[] args) throws InterruptedException {
2   Thread t1 = new Thread(()->{
3        System.out.println("Thread 1 is running");
4        for(int i=0;i<10000;i++){
5            if(Thread.currentThread().isInterrupted()){
6                break;
7            }
8        }
9        System.out.println("Thread 1 is ending");
10   });
11   t1.start();
12   t1.interrupt();
13   Thread.sleep(2000);
14   System.out.println("main thread is running");
15}
16
17
  1. t1和main线程在互相抢占cpu执行,t1被标记暂停了,main线程睡眠了。
  2. 此时大概率t1线程在跑任务,输出Thread 1 is running。
  3. t1继续在执行for循环时,判断状态发现自己被标记暂停了,立马退出,执行Thread 1 is ending
  4. 然后main线程醒来后,执行main thread is running。

2.4 volatile

volatile保证可见性:我写完后,别人能读取到新值。但是它还是无法解决并发写的问题。

1public class VolatileStop {
2    static volatile boolean running = true;
3    public static void main(String[] args) throws InterruptedException {
4        Thread worker = new Thread(() -> {
5            long n = 0;
6            while (running) {
7                n++;
8            }
9            System.out.println("worker 看到 false,退出 n=" + n);
10        });
11        worker.start();
12        Thread.sleep(100);
13        running = false;
14        worker.join();
15        System.out.println("main 结束");
16    }
17}
18

三、同步与锁

同步与锁是并发编程非常重要环节,首先需要掌握3个核心api。

3.1 synchronized

3.1 概念

锁可以理解成钥匙,synchronized (x)就是多个线程去抢占这个x锁,每次只能有1个线程能抢到,方法执行结束后后释放锁,没有抢到的线程无法进入方法内。

1synchronized(x){
2    //do
3}
4
5

3.2 条件

成功的锁必须满足两个条件

  1. 多线程抢的必须是同一个对象(同一把钥匙)
  2. 保护的是同一份共享数据

锁代码块

线程t1和t2共享变量count,而且锁的作用对象都是lock。

1private static int count = 0;
2private static Object lock = new Object();
3public static void increment(){
4    synchronized(lock){
5        for(int i=0;i<10000;i++){
6            count++;
7        }
8    }
9}
10public static void main(String[] args) throws InterruptedException {
11   Thread t1 = new Thread(()->{
12    increment();     
13   });
14   Thread t2 = new Thread(()->{ 
15    increment();
16   });
17   t1.start();
18   t2.start();
19   t1.join();
20   t2.join();
21   System.out.println(count);
22}
23
24

2. 锁函数

这里t1和t2线程的锁的对象是static的Class类上,二者还是同一个对象,所以依旧正常。

1private static int count = 0;
2private static Object lock = new Object();
3public static synchronized void increment(){
4    for(int i=0;i<10000;i++){
5      count++;
6    }
7}
8public static void main(String[] args) throws InterruptedException {
9   Thread t1 = new Thread(()->{
10    increment();     
11   });
12   Thread t2 = new Thread(()->{ 
13    increment();
14   });
15   t1.start();
16   t2.start();
17   t1.join();
18   t2.join();
19   System.out.println(count);
20}
21
22

3. 错误锁

多线程时,锁的对象明显地址是不同的,无效锁。

1synchronized (new Object()) {
2    count++;
3}
4

3.2 wait

wait和sleep有本质差异,sleep是释放cpu抢占资源,而wait是释放锁的意思。线程wait释放锁后,就一直被阻塞,苏醒后继续抢到锁,然后执行未完成代码。

wait苏醒后从被wait地方继续执行代码,而不是从函数或者synchronized开始执行。

如下代码模拟生产者与消费者,由于生产者的方法入口添加sleep,所以消费者一定先抢到了锁,输出 begin decrement,来到锁里面输出begin decrement1,但是由于队列空,只能wait,把锁给释放了。此时生产者拿到锁,输出了begin increment和begin increment1,给队列生产1个数据,通知消费者消费。注意消费者苏醒后直接从lock.wait()这行代码后继续执行,继续while循环判断。

1begin decrement
2begin decrement1
3begin increment
4begin increment1
5
1public class Main {
2    private static int max = 1;
3    private static Object lock = new Object();
4    private static List<Integer> list = new ArrayList<>();
5    public static void increment() {
6        try{
7            Thread.sleep(1000);
8        } catch (InterruptedException e) {
9            e.printStackTrace();
10        }
11        System.out.println("begin increment");
12        synchronized (lock) {
13            System.out.println("begin increment1");
14            while (list.size() >= max) {
15                try {
16                    lock.wait();
17                } catch (InterruptedException e) {
18                    Thread.currentThread().interrupt();
19                    return;
20                }
21            }
22            list.add(1);
23            lock.notifyAll();
24        }
25    }
26
27    public static void decrement() {
28        System.out.println("begin decrement");
29        synchronized (lock) {
30            System.out.println("begin decrement1");
31            while (list.size() == 0) {
32                try {
33                    lock.wait();
34                } catch (InterruptedException e) {
35                    Thread.currentThread().interrupt();
36                    return;
37                }
38            }
39            list.remove(list.size() - 1);
40            lock.notifyAll();
41        }
42    }
43    public static void main(String[] args) throws InterruptedException {
44       Thread t1 = new Thread(()->{
45        increment();
46       });
47       Thread t2 = new Thread(()->{ 
48        decrement();
49       });
50       t1.start();
51       t2.start();
52    }
53}
54

为什么边界条件都是while,而不是if?

if会造成条件失效,模拟下如果3个生产者,1个消费者,队列最大是1。

11. 生产者t1,生产1个,通知其它线程抢锁
22. 生产者t2,发现队列满了,把自己wait,
33. 生产者t3,发现队列还是满了,把自己wait,
44. 消费者进来,消费1个,通知其它线程抢锁
55. t2苏醒,直接走if后面的代码,队列生产1个
66. t3苏醒,直接走if后面的代码,队列生产2个,报错。
7
8

3.3 notify

notify是wait对立面,它手上有锁,告诉被wait的线程,你可以去外面排队了,我马上就释放锁了,注意此时还没有释放锁,只有synchronized代码块执行结束才释放锁。而被wait的线程收到notify通知后,不睡觉了,在门口等待抢锁动作。

notify()notifyAll()
叫醒几个一个(哪个不确定)这把锁上 所有 wait 的
风险可能叫错人:叫醒了另一个生产者,消费者还在睡大家醒了再用 while 判断,不会干错
入门少用优先用这个

四、死锁

死锁必须满足

  1. 互斥: 锁同时只能一人拿
  2. 持有并等待: t1拿着 lock1 不放,再等 lock2
  3. 不可剥夺: 不能把别人的锁抢走,只能等他自己放
  4. 循环等待: t1 等 t2,t2 等 t1,围成环
1public class Main {
2    private static Object lock1 = new Object();
3    private static Object lock2 = new Object();
4    public static void main(String[] args) throws InterruptedException {
5       Thread t1 = new Thread(() -> {
6        synchronized(lock1){
7            try {
8                Thread.sleep(1000);
9            } catch (InterruptedException e) {
10                e.printStackTrace();
11            }
12            synchronized(lock2){
13                System.out.println("Thread 1 locked lock2");
14            }
15        }
16       });
17
18       Thread t2 = new Thread(() -> {
19        synchronized(lock2){
20            try {
21                Thread.sleep(1000);
22            } catch (InterruptedException e) {
23                e.printStackTrace();
24            }
25            synchronized(lock1){
26                System.out.println("Thread 2 locked lock1");
27            }
28        }
29       });
30
31       t1.start();
32       t2.start();
33    }
34}
35

06-并发》 是转载文章,点击查看原文


相关推荐


当 Mac mini 的价格不再 mini -- 肘子的 Swift 周报 #152
东坡肘子2026/9/8

当 Mac mini 的价格不再 mini 上月底,苹果发布了新的 Mac mini 和 Mac Studio。除了强悍的性能更新,明显上涨的价格同样给人带来了不小的震撼。内存与存储成本上涨无疑是本轮涨价的重要原因,但在 AI 时代,开发者对硬件的需求也在悄然发生变化。更大的内存、更高的存储容量,不断推高我们心目中“够用配置”的标准,最终也让实际购买价格变得更加夸张。 过去几个月,开发中最让我头痛的一件事,是 MacBook Pro 风扇持续转动带来的噪音。过去多年的使用中,只有在少数高负载场景


Eino + DeepFlux 实战全景复盘:100 篇,从 5 分钟 Demo 到企业级平台(第100篇-E86)
leeyi2026/8/31

这是第 100 篇,收官篇。不拆新源码——反过来,把 99 篇走过的路和 DeepFlux 这个真实仓库放在一起盘一遍。 (一)100 篇走了哪几步 回头看,系列其实是一条"从外向内、再向外"的路线: 第一季(01-14):从平台往下看。 先拆 DeepFlux 这个企业级平台——DDD 四层、Session 状态机、ReAct 主循环、HITL、SSE 推流、Hook 护栏、多 LLM、RAG、记忆、RLS 多租户、审计、可观测。当时的问题是"企业级 Agent 平台长什么样",答案是 14


FastAPI 后台任务的边界,以及 Celery、Redis 与自建调度系统的选择
卷无止境2026/8/23

FastAPI 的 BackgroundTasks 很轻巧,但它更像是响应发送后的顺手处理机制,并不是一套可靠的任务队列。写日志、发送不太关键的通知、清理临时文件,它用起来很舒服;一旦任务涉及可靠投递、自动重试、跨机器执行、定时调度或运行状态追踪,就该把工作交给 Celery、Redis Streams,或者独立的任务调度服务。 FastAPI 的 BackgroundTasks 做了什么 FastAPI 的 BackgroundTasks 建立在 Starlette 的后台任务机制之上。开发


AI 工具越好用越要严格把控安全边界
MobotStone2026/8/10

前端时间,有个小伙伴问我:“公众号文章、标书这些东西,是不是都可以直接丢给 AI,让它帮忙处理?” 刚听到这个问题,我觉得挺常见。现在大家写文章、改材料、做总结,第一反应就是找 AI,确实能省不少时间。 但继续聊了几句,我才发现,事情没那么简单。 他已经把公司一整份标书上传给 AI 了。 而这份标书里,不只有网上能查到的公司介绍、业务信息,还包括营业执照、员工社保资料,甚至身份证件等敏感材料。 这两类内容,看起来都是“让 AI 帮忙处理文档”,风险却完全不是一个级别。 比如,一篇准备公开发布的公


Linux——基础开发工具(上)
进击的荆棘2026/7/31

💁‍♂️个人主页:进击的荆棘 👇作者其它专栏: 《数据结构与算法》《算法》《C++起始之路》《Linux》 目录 1.软件包管理器 2.编辑器Vim 3.编译器gcc/g++ 4.自动化构建--make/Makefile 5.Linux第一个系统程序--进度条 6.版本控制器Git 7.调试器-gdb/cgdb使用 1.软件包管理器 1.1什么是软件包 ●在Linux下安装软件,一个通常的办法是下载到程序的源代码,并进行编译,得到可执行程序。 ●但


c++11终章
牢姐与蒯2026/7/23

一.lambda 1.lambda的语法 ①.定义 lambda表达式是一个匿名函数对象,跟普通函数只能定义在全局,或者类里不同,它能够定义在函数的内部。 ②.类型 lambda表达式在使用层而言没有类型,一般用auto或者模板参数定义的对象去接收lambda对象。 ③.lambda的格式 表达式格式跟普通函数相比,少了函数名(匿名),多了捕捉列表。 一个小示例: 注:此处修正一下,返回值部分是“->返回值类型”。 2.lambda的用途 从图中可看出利用lam


GitHub 热榜项目 - 周榜(2026-07-11)
CoderJia_2026/7/15

GitHub 热榜项目 - 周榜(2026-07-11) 生成于:2026-07-11 统计摘要 共发现热门项目: 21 个 Token赞助:siliconflow 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 本期热点趋势总结 本期 GitHub AI 热榜很清晰:Agent 化、低成本推理、隐私本地化、办公自动化正在加速融合。🧠 代表项目里,addyosmani/agent-skills、alir


Objective-C 之 Block 详解
唐诺2026/7/7

iOS核心技能:Objective-C Block完全详解 从语法到原理,从内存到选型,一篇打通Block所有知识点 Block是Objective-C最核心的特性之一,也是每个iOS开发者必须掌握的知识点。它既是回调利器,也是内存陷阱的高发区。本文将系统讲解Block的一切,并在最后与Method进行全方位对比,帮助你彻底掌握Block的选型与使用。 一、什么是Block? Block是Objective-C对C语言函数指针的扩展,它本质上是一个带有自动变量值的匿名函数对象。简单理解:B


用户说 App 卡,但说不清在哪?我把 Flutter 监控 SDK 升级成了链路观测工作台
小林的编程开发日记2026/6/29

用户说 App 卡,但说不清在哪?我把 Flutter 监控 SDK 升级成了链路观测工作台 大家好,去年我写过一篇 Flutter 全链路监控 SDK 那一版可以理解成 v1。 v1 的目标很直接:Flutter App 里发生了什么问题,我要尽可能采到。 比如: Flutter framework error; Dart uncaught error; 启动耗时; 页面加载耗时; Dio / http 请求耗时和状态码; 页面 PV 和停留时长; 用户关键点击; Flutter fram


Python 五大核心数据结构完全指南:列表(List)、字符串(String)、元组(Tuple)、集合(Set)、字典(Dictionary)
FrontPy2026/6/20

Python 核心数据结构完全指南 一、列表 (List) 1.1 定义与特点 列表是 Python 中最常用的数据容器,可以一次性存储多个元素。 定义: 列表名 = [元素1, 元素2, 元素3, ...] 例如: s = [54, 15, 75, 108, 23, 78, 75] 特点: 可以存储不同类型的元素(数字、字符串、布尔值等混合存放)。元素有序,排列顺序固定。元素可以重复。元素可以被修改(增、删、改)。属于序列类型:可通过索引访问。 1.2 列表的索引 正向索引:从 0 开始,

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读