当前位置: 首页 > news >正文

JUC并发编程知识一(待完善)

进程与线程

1.什么是进程?

进程是系统运行程序的基本单位,一个程序的运行就是一个进程。如:点开微信.exe文件、浏览器.exe文件

  • 程序是静态:程序是静态的指令集合,是存储在磁盘上的可执行文件。如硬盘上的 “微信.exe”“浏览器.exe”
  • 进程是动态:进程是程序的动态执行过程,当程序被操作系统加载到内存中,系统为其分配资源

2.什么是线程?

线程是 CPU 调度的最小单位,一个进程可以有多个线程,多个线程共享进程的堆和方法区资源,每个线程都有自己程序计数器、本地方法栈、虚拟机栈。


3.Java 程序启动时的默认线程数

Java 程序启动时最核心的两个线程:main 线程(用户线程),GC 线程(守护线程)

最少情况下:大约 4~5 个线程数(main + GC + Finalizer + Reference Handler + Signal Dispatcher)

对比项用户线程(普通线程)守护线程(Daemon)
生命周期主线程结束后仍可运行只要所有用户线程结束,它自动退出
应用场景执行业务逻辑做后台任务,如日志、监控、GC等
设置方式默认是用户线程手动调用setDaemon(true)设置
JVM 退出行为至少一个用户线程在,JVM 不退出全部用户线程结束,JVM 自动退出

4.CPU和线程的关系

线程:线程是程序中的一段逻辑(如: for (int i = 0; i < 3; i++) ),逻辑不能够自己进行运行,需要通过CPU来执行。

CPU:cpu是执行这线程的硬件,cpu主要功能就是从内存中读取指令、解码指令、执行指令

需要注意的是:一个cpu只能执行一个线程任务


2.上下文切换

1.什么是上下切换?

上下文切换是指CPU从一个线程切换到另一个线程时,保存当前线程的执行状态并加载下一个线程的执行状态,当前线程通过记录的执行状态恢复执行。


2. “上下文” 是什么?

上下文是线程能够正常运行的核心条件之一,上下文包含了线程能够运行的所有状态数据,包含有程序计数器、本地方法栈、虚拟机栈


3.上下文切换的一些原因

  1. 时间片用尽,当线程的时间片用尽时,操作系统会将其状态保存,切换到另一个线程。
  2. 调用了sleep、wait,线程进入等待状态,线程让出cpu
  3. 线程执行完成

4. 上下文存储了哪些数据

  1. 程序计数器:记录当前线程下一条要执行的指令地址,确保线程切换后能从暂停处继续执行。
  2. 虚拟机栈:存储方法调用的栈帧,包含局部变量表、操作数栈、方法返回地址等。
  3. 本地方法栈:用于支持 Native 方法执行的栈结构。
  4. 寄存器状态:CPU 寄存器中与当前线程相关的数据(如通用寄存器、状态寄存器等),记录运算中间结果等临时数据。
  5. 线程私有数据:如线程 ID、优先级、状态标记(新建 / 运行 / 阻塞等)。
  6. 锁相关信息:线程持有的锁、等待的锁队列等(尤其在并发场景中)。

3.如何创建线程

1. 前置知识

匿名内部类

匿名内部类是没有名字的局部类,它是在定义类的同时创建对象,常用来快速实现接口或抽象类的一个实例

语法格式:接口/父类 类型 变量名 = new 接口/父类() { // 实现或重写方法 };
public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello Lambda!"); } }; r.run(); }
传统写法 public class MyRunnable implements Runnable { @Override public void run() { System.out.println("Hello Lambda!"); } } public class Main { public static void main(String[] args) { Runnable r = new MyRunnable(); r.run(); // 或者 new Thread(r).start(); } }

Lambda 表达式

用于替代匿名内部类。它让我们写代码时更简洁、更函数式。

语法格式:(参数列表) -> { 代码块 }

简化:参数 -> 表达式

public static void main(String[] args) { Runnable r = () -> System.out.println("Hello Lambda!"); r.run(); }

函数式接口

函数式接口是只包含一个抽象方法的接口。它可以被 Lambda 表达式、方法引用等简洁语法所实现。

@FunctionalInterface是可选的注解,用于明确声明该接口是函数式接口,如果你写了多个抽象方法,编译器会报错。

接口名抽象方法说明
Function<T,R>R apply(T t)输入 T,返回 R
Consumervoid accept(T t)接收 T,无返回
SupplierT get()无参数,返回 T
Predicateboolean test(T t)输入 T,返回布尔值
Runnablevoid run()无参无返回(线程)
CallableV call()可抛异常,有返回值
官方定义(来自 Java 8) @FunctionalInterface public interface MyFunction { void apply(); }

示例:

@FunctionalInterface interface MyPrinter { void print(String msg); }
使用 lambda 表达式实现它 public static void main(String[] args) { MyPrinter p = (msg) -> System.out.println("打印: " + msg); p.print("Hello!"); }

方法引用

它是Lambda 表达式的简化写法

语法格式:类名/对象名::方法名

类型示例等价 Lambda 表达式说明
1. 引用静态方法ClassName::staticMethodx -> ClassName.staticMethod(x)适用于静态方法
2. 引用实例方法(特定对象)instance::methodx -> instance.method(x)对象已存在
3. 引用实例方法(任意对象)ClassName::method(obj, x) -> obj.method(x)用于 Stream 等场景
4. 引用构造方法ClassName::new() -> new ClassName()用于构造对象

stream流

Java 8 引入的一种用于处理集合数据的抽象概念,它代表一系列元素的管道式计算,支持链式调用、函数式编程、并行处理等操作

步骤描述举例
1. 获取流从集合或数组等生成流list.stream()
2. 中间操作处理数据(可多个)filter()map()sorted()
3. 终止操作触发执行forEach()collect()count()

示例:

import java.util.*; import java.util.stream.*; 找出长度大于 3 的字符串并转换为大写 public class Main { public static void main(String[] args) { List<String> fruits = Arrays.asList("apple", "kiwi", "banana", "fig", "grape"); fruits.stream() // 1. 获取流 .filter(f -> f.length() > 3) // 2. 中间操作:过滤 .map(String::toUpperCase) // 2. 中间操作:转换大写 .forEach(System.out::println); // 3. 终止操作:输出 } } 传统方式 import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<String> list = Arrays.asList("apple", "kiwi", "banana", "fig"); List<String> result = new ArrayList<>(); for (String item : list) { if (item.length() > 3) { result.add(item.toUpperCase()); } } for (String s : result) { System.out.println(s); } } }

2. 线程创建方式

创建线程的方式有很多,但这些方式并不是真正的创建出线程,严格来说Java只有一种创建线程的方式,那就是“new Thread.start ()”,不管是哪种方式,最终还是依赖于“new Thread.start ()”

1. 继承 Thread 类,重写run()

public class ThreadExample extends Thread { @Override public void run() { System.out.println("子线程开始执行,"+ "线程名:" + Thread.currentThread().getName()); } } class ThreadTest{ public static void main(String[] args) { ThreadExample threadExample = new ThreadExample(); Thread thread = new Thread(threadExample); System.out.println("主线程开始执行," + "线程名:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } 启动新线程,由新线程执行run()方法 threadExample.start(); } }

2. 实现 Runnable 接口,重写run()

public class RunnableExample implements Runnable { @Override public void run() { System.out.println("子线程开始执行,"+ "线程名:" + Thread.currentThread().getName()); } } class ThreadTest{ public static void main(String[] args) { RunnableExample threadExample = new RunnableExample(); Thread thread = new Thread(threadExample); System.out.println("主线程开始执行,"+ "线程名:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } 启动新线程,由新线程执行run()方法 thread.start(); } }

3. 实现 Callable 接口 + Future ,重写call()

实现细节:重写call()方法,Callable对象封装到Future中,创建Thread对象,通过FutureTask的 get()方法获取返回值。

注意事项:在调用thread.start()之前就调用了futureTask.get(),会导致主线程一直阻塞,永远无法执行子线程。

public class CallableExample implements Callable { @Override public Object call() { System.out.println("子线程开始执行,"+"线程名:"+ Thread.currentThread().getName()); return 1; } } class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { CallableExample callableTest = new CallableExample(); FutureTask futureTask = new FutureTask<>(callableTest); Thread thread = new Thread(futureTask); System.out.println("主线程开始执行," + "线程名:"+ Thread.currentThread().getName()); Thread.sleep(3000); thread.start(); Object o = futureTask.get(); System.out.println("主线程获取返回值"+ o); } }

4. 使用线程池创建

线程池创建线程的过程由其内部机制控制,核心是通过线程工厂生成新线程

3.Runnable和Callable的区别

1.是否有返回值

  • Runnable接口中的run()方法返回值类型为void,执行完成后没有返回值。
  • Callable接口中的call()方法返回值类型为泛型,执行完成后有返回值。返回值通过Future获取。

2.是否能捕获异常

  • Runnable接口中run()方法抛出运行时异常,但无法捕获
  • Callable接口中call()方法抛出异常通过Future捕获异常

4. 为什么Java除了最简单的继承Thread,还要设计RunnableCallable+Future这些复杂的方式?

  1. Java里,一个类只能继承一个父类。如果你直接继承了Thread,就没法继承别的类了,限制了代码的灵活性。
  2. 实现接口更灵活,可以多实现接口,还能继承别的类
  3. 通常完成业务需要返回值,通过Callable方式能够获取返回值

4.线程生命周期

1. 线程生命周期有哪几种状态?

  1. 新建状态:指创建线程对象,还未调用 start(),线程还没开始运行。
  2. 就绪状态:指线程对象调用 start()方法进入就绪状态,等待CPU调度
  3. 阻塞状态:指代码加了同步锁,没有获取到锁的线程会被阻塞。(线程获取到锁后阻塞状态转换为就绪状态)
  4. 等待状态:指线程调用wait()、join(),线程需要等待被唤醒。(线程被唤醒,等待状态转换为就绪状态)
  5. 含时间的等待状态:指线程调用sleep( time ),线程会进入睡眠。(当超过睡眠时间,等待状态转换为就绪状态)
  6. 终止状态:指线程执行run()完成或者线程异常退出,然后线程生命周期结束。(当一个线程的状态变为终止状态后,无法通过任何方式让它再次执行,不能重新调用start()方法让其再次运行)

2. 线程执行任务时什么情况下让出CPU?

  1. 线程执行run方法完成或线程异常退出时,线程会主动让出CPU
  2. 当线程进入阻塞状态或等待状态时,会被动让出 CPU
  3. 优先级较低的线程,会被动让出CPU

5.wait()、sleep()、join().......

1.wait()、sleep()、join()使用

wait()是 Java 中Object类的一个方法,让当前线程暂时放弃对象锁并进入阻塞状态,等待其他线程的唤醒后再继续执行。

  • 必须在synchronized同步代码块或方法中调用,且当前线程必须持有该对象的锁(否则会抛出IllegalMonitorStateException
Object lock = new Object(); // 线程A:进入等待状态 new Thread(() -> { synchronized (lock) { try { System.out.println("线程A:进入等待"); lock.wait(); // 释放锁,进入WAITING状态 System.out.println("线程A:被唤醒,继续执行"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); // 线程B:唤醒等待的线程A new Thread(() -> { synchronized (lock) { System.out.println("线程B:准备唤醒"); lock.notify(); // 唤醒等待队列中的线程A System.out.println("线程B:唤醒完成"); } }).start();

sleep()Thread类的静态方法,用于让当前线程暂停执行,指定暂停时间(毫秒或纳秒),时间结束后线程自动唤醒并进入就绪状态,等待 CPU 调度。

new Thread(() -> { while (true) { System.out.println("定时任务执行中..."); try { Thread.sleep(3000); // 每3秒执行一次 } catch (InterruptedException e) { break; // 被中断时退出循环 } } }).start();

join()Thread类的方法,用于让当前线程等待目标线程执行完毕后再继续运行,核心作用是控制线程的执行顺序。

Thread t = new Thread(() -> { // 子线程任务 System.out.println("子线程执行中..."); try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } }); // 启动子线程 t.start(); // 主线程调用 t.join(),表示主线程等待 t 执行完毕 t.join(); System.out.println("主线程:子线程已完成,主线程开始执行"); }

2.wait() 、sleep()、join()的区别

1.所属类不同

  • wait()Object的方法
  • sleep()Thread的静态方法
  • join() 是Thread的方法

2.对锁的影响不同

  • wait()会释放当前持有的锁
  • sleep()不会释放任何锁
  • join()不会释放任何锁

3.唤醒机制不同

  • wait():必须由其他线程调用notify()notifyAll()唤醒
  • leep():睡眠时间到后自动唤醒
  • join():一个线程完成任务后自动唤醒

3.notify()和notify() 的区别

  • notify():从等待队列中,随机叫醒一个在等待的线程
  • notifyAll()叫醒所有在等待的线程

4.run()和start()的区别

1、调用次数限制不同

  • start()只能调用一次
  • run()可以调用多次

2、是否启动新线程

  • 调用start():会启动一个新线程,新线程执行run()中的逻辑
  • 调用run():不会启动新线程,而是在当前线程中直接执行run()中的逻辑

6. Java中线程之间如何进行通讯?

1. 线程之间为什么需要进行通讯?

开发一个电商系统,一个线程负责接收用户请求,一个线程负责库存数据更新,一个线程负责发送通知。如果各线程之间不进行相互合作,会导致电商系业务流程会混乱,数据不一致,最终用户流失。

“多个线程之间怎么‘沟通’ ”,比如怎么传递数据、怎么配合执行(谁先做谁后做)、怎么告知对方 “我做完了”“你可以开始了”。


2. 线程之间如何实现通讯

1、使用 wait()、notify()、notifyAll()方法

三种方法是Object类中定义的方法,必须与synchronized同步锁配合使用,wait()让线程进入等待状态,当满足条件时,通过notify()、notifyAll()唤醒线程,达到线程之间通讯

场景:适用于线程间的协调,如:生产者-消费模型,一个线程负责生产资源、一个线程负责消费资源


2、使用Condition对象

Condition对象有自己的wait()、notify()、notifyAll()方法

  • await():让线程进入等待状态并释放锁
  • signal():唤醒一个等待的线程
  • signalAll():唤醒所有等待的线程

三种方法必须与lock锁配合使用,一个lock锁能够创建多个Condition对象等待队列,能够按条件进行分组管理线程。能够代替Object类中wait()、notify()、notifyAll()方法。


3、使用BlockignQueue对象

通过BlockignQueue对象提供put()向队列中添加元素,当队列满时会阻塞生产者;take()向队列中取出元素,当队列空时会阻塞消费者,该方式不需要手动处理线程等待和唤醒逻辑。


4、volatile关键字

当一个线程修改了用volatile修饰的变量其他线程会立即看到该变量的状态变化,实现简单的信息传递。

7.线程优先级

优先级范围:线程优先级范围:1~10,默认线程优先级为5

设置与获取:

  • 通过setPriority(int newPriority)方法设置优先级;
  • 通过getPriority()方法获取当前优先级。
===================================Thread源码======================================== /** * The minimum priority that a thread can have. * 线程可以拥有的最小优先级 */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. * 分配给线程的默认优先级 */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. * 线程可以拥有的最大优先级 */ public final static int MAX_PRIORITY = 10;

8.虚假唤醒

指:线程在没有被notify()notifyAll()唤醒的情况下,自己跳过条件判断从wait()状态返回

1.为什么if会导致虚假唤醒问题?

wait()返回后,不会知道自己是“被真正唤醒”还是“被虚假唤醒”

  • if只判断一次条件;

  • 一旦wait()返回(无论真假),if不会再检查条件

  • 如果条件没满足就往下执行,就出问题了。

2. 正确方式(用while保证条件检查)

while (number !=0){ this.wait(); } number ++; System.out.println(Thread.currentThread().getName() + "," + number); this.notifyAll();

9.死锁

1.死锁四大必要条件

  1. 互斥条件:一个资源只有一个线程持有
  2. 持有并等待:一个线程持有资源的同时,请求新的资源
  3. 不可剥夺:线程持有的资源不能被强制剥夺,除非自己释放资源
  4. 循环等待:每个线程都在等待下一个线程释放资源,形成循环

2.线程死锁

两个或两个以上的线程持有的资源都是对方所需要的,但都不释放资源,线程之间相互等待,这个现象就是死锁。


3.如何避免线程死锁

打破产生死锁的四大条件中的一个就可避免死锁

  1. 按顺序申请资源:每个线程按相同顺序获取锁,如先线程1获取lock1锁,其他线程必须等待lock1释放才能够获取lock2锁 (破坏 “循环等待”)
  2. 一次性获取所有资源:线程在执行前,先尝试获取所有需要的资源,要么全部获取,要么一个都不持有(破坏 “持有并等待”)
  3. 使用尝试锁机制:使用ReentrantLocktryLock(timeout)方法:尝试一段时间内获取锁,如果超时未获取到锁,则放弃获取或释放已持有的锁。 (破坏 “不可剥夺”)
  4. 减少锁的持有时间:线程持有锁的时间缩短,其他线程也能够在短时间内获取到锁


4.死锁检测方式

方式一:Java自带的 jps命令 + jstack命令

jps命令:查看 Java进程的ID

jstack命令:根据线程ID,排查线程死锁、阻塞、CPU 飙高、程序卡死等问题。

方式二:第三方工具

阿里巴巴的Arthas

http://www.zskr.cn/news/1401626.html

相关文章:

  • 无锡婚姻律师事务所口碑排行:专业实力实测对比 - 奔跑123
  • 还在为Windows文件管理烦恼?5个技巧让QTTabBar成为你的效率神器
  • AT89C51数字电子时钟和proteus仿真(74HC573)
  • AAOS系列之(七) --- AudioRecord录音逻辑分析(一)
  • 终极指南:Hap QuickTime编解码器 - 现代GPU加速视频压缩完整教程
  • Awesome RSS Feeds完整使用指南:分类订阅、国家新闻与个性化配置技巧
  • 工业网络新引擎—基于IPQ5018的WiFi 6工业路由器核心优势与场景化部署解析
  • 探索GMPlot:在Python中绘制地理数据的高效工具
  • 从像素到代码:Mesen如何让NES游戏在现代电脑上重生
  • arXiv论文管理神器:如何用开源工具高效追踪AI研究动态
  • 终极指南:WeChatPad - 简单三步实现微信平板模式,破解设备限制
  • 终极指南:如何免费解锁《艾尔登法环》帧率限制,畅享高帧率游戏体验
  • Keyboard Chatter Blocker:三招解决机械键盘连击问题,让你的旧键盘焕发新生
  • CANN Catlass后处理组件
  • 5个关键问题:Playnite插件如何彻底改变你的游戏库管理体验?
  • FanControl:Windows风扇控制终极指南,3步实现零噪音电脑
  • 基于Nuxt 3与VueFlow构建Claude Code智能体可视化控制平面
  • 从HDF到可视化:手把手解析CALIPSO VFM星载激光雷达数据处理全流程
  • 鸣潮自动化助手终极指南:从新手到高手的完整解决方案
  • 3步实现HoneySelect2完整汉化与MOD整合:HS2-HF Patch终极指南
  • 阵列信号处理笔记-波达方向DOA-子空间方法:从MUSIC到现代高分辨算法
  • 抖音无水印视频下载终极方案:douyin-downloader专业指南
  • LinkSwift网盘直链下载助手:九大网盘一站式下载解决方案终极指南
  • 如何用OBS-captions-plugin为直播添加实时字幕:完整免费教程
  • 高效AI专著生成:实测优质工具,快速产出20万字专业专著
  • 毕业论文的加速引擎!常用的AI写作辅助网站,成稿速度超迅速
  • 英雄联盟终极智能助手:League Akari 完全使用指南
  • 5步掌握ESP32-Arduino核心:从硬件配置到物联网应用
  • 永磁节能潜水搅拌机http://www.llhjkj.com/的故障性能特点 - 品牌推荐大师
  • 如何快速掌握MoveIt2:面向初学者的完整ROS 2运动规划框架指南