Java内功修炼(1)——时光机中的并发革命:从单任务到Java多线程

Java内功修炼(1)——时光机中的并发革命:从单任务到Java多线程

1.进程&线程

1.1 背景介绍

1950年代,计算机系统通常是单任务的。早期计算机一次只能执行一个程序,需要人工切换。这种设计简单但效率低下1960年代,多任务系统的概念开始萌芽。早期的大型机操作系统如IBM的OS/360引入了分时技术,允许多个用户同时使用计算机资源。虽然计算机实际一次只能干一件事,但靠这种“闪电式切换”,用户感觉电脑在同时处理多个任务1970年代,Unix操作系统诞生,采用了多任务设计。Unix通过进程调度时间片轮转机制,允许多个程序并发执行。这一设计成为现代多任务系统的基础单任务(进程)系统:同一时间只能运行一个程序或任务,任务必须按顺序完成。用户需等待当前任务结束后才能启动新任务。系统资源由一个任务独占,缺乏并发能力,适用于简单应用场景
多任务(进程)系统:允许同时运行多个程序或任务,通过时间片轮转优先级调度实现并发协同式:应用程序需要主动释放CPU资源。设计简单,但稳定性较差抢占式(现代主流):由操作系统强制分配资源。操作系统可以强制中断任务,确保系统响应能力,进一步提高了并发性能。现代操作系统如Windows、Linux均采用抢占式多任务,支持更高效的资源利用和用户体验

1.2 进程/任务

进程(Process)/任务(Task):进程是计算机中正在运行的程序的实例(应用程序与进程的关系近似于Java中类和对象的关),是操作系统进行资源分配的基本单位应用程序 ≈ 类的代码定义(静态模板)进程 ≈ 类的运行时实例/对象(动态实体)打开任务管理器,这里显示的每一个运行中的exe(可执行文件),就叫做进程
当一个进程被创建的时候,操作系统会为它创建一个/多个PCB(Process Control
Block进程控制块)
,类似C语言的结构体/Java中的类,PCB里面会存放该进程的各种属性状态就绪状态:进程的资源准备完成,随时可以运行运行状态:正在运行阻塞状态:当进程等待某个事件(如Scanner等待用户输入)时,进程暂停执行, 等待用户输入完成终止状态:进程执行完毕或被终止,操作系统会释放进程占用的资源优先级:当存在多个进程时,优先级高的进程优先执行,优先级低的进程需要等待记账信息:记录进程已使用的系统资源,如CPU,内存等设备的使用情况上下文信息:进程在被调度执行前的状态信息,以便进程可以从上次暂停的地方继续执行

文件描述符表:文件描述符表通常是一个数组链表,每个节点对应一个文件描述符,进程通过文件描述符来访问文件,实现进程对文件的增删查改等操作

在这里插入图片描述

内存指针:指向的空间就是该进程创建时所申请的内存空间,指明了这个空间中哪些位置存放数据,哪些位置存放指令/代码

在这里插入图片描述

PID:进程的id,可以认为是进程的身份标识,每个进程的id是独一无二的,不能重复

在这里插入图片描述

1.3 进程优缺点&线程

优点:独立性:每个进程拥有独立的地址空间和资源,一个进程崩溃不会直接影响其他进程的运行,提高了系统的稳定性资源隔离:操作系统为每个进程分配独立的资源(如内存、文件描述符等),避免资源竞争和冲突,增强了安全性

缺点:资源开销大:创建和销毁进程需要分配独立的地址空间和资源,上下文切换涉及保存和恢复大量寄存器状态,性能损耗较高1.创建进程,以及PCB2.连接各个进程,利用链表或者其他数据结构将PCB连接起来线程(Thread)/轻量级进程:由操作系统调度,不用额外分配资源,是操作系统调度执行的基本单位。一个进程可以包含多个线程,所有线程共享进程的资源(由线程控制块TCB管理),但每个线程拥有独立的执行栈和程序计数器
优点:轻量级:线程的创建、切换和销毁比进程更高效并发执行:多线程允许程序在同一时间内执行多个任务(任务和进程是否相等需要根据具体语境判断,这里的任务指的是代码块),提高CPU利用率共享资源:线程可以直接访问所属进程的全局变量、堆内存等

缺点:稳定性风险:单个线程的崩溃可能导致整个进程终止,因为线程间缺乏隔离性资源竞争与同步问题:线程共享同一进程的内存空间和资源,多个线程同时访问共享数据时可能引发竞争条件,导致数据不一致。必须使用锁、信号量等同步机制,增加了编程复杂度,且不当使用可能引发死锁或性能下降

3.删除和增加进程,将该进程的PCB从链表中删除或者插入

在这里插入图片描述

2.Java中的线程实现:Thread类

2.1 创建线程

构造方法如下:
1.直接继承Thread类
优点:实现简单,直接重写run()方法即可定义线程任务

缺点:线程逻辑与Thread类耦合,不利于代码复用Java单继承特性限制了扩展性,无法再继承其他类
//Thread类实现了Runnable接口,所以需要重写run方法classMyThreadextendsThread{@Overridepublicvoidrun(){while(true){System.out.println("Hello MyThread");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}}publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newMyThread();//启动线程 thread.start();}}
2.实现Runnable接口
优点:避免单继承限制,可同时实现其他接口或继承类线程任务与Thread解耦,便于复用

缺点:需额外创建Thread类实例来启动线程,代码稍显冗余
classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){while(true){System.out.println("Hello MyRunnable");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}}publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newThread(newMyRunnable());//启动线程 thread.start();}}
3.匿名内部类创建Runnable子类对象
优点:可直接访问外部类的final或有效final变量(变量捕获)

缺点:代码可读性较差,尤其是逻辑复杂时
publicclassThreadDemo{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread =newThread(){//匿名内部类@Overridepublicvoidrun(){while(true){System.out.println("Hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}}; thread.start();}
4.lambda表达式创建Runnable子类对象
优点:可直接访问外部类的final或有效final变量(变量捕获)

缺点:仅适用于接口中只有一个抽象方法的情况
publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newThread(()->{while(true){System.out.println("Hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}}); thread.start();}}

2.2 获取线程名字和id

publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newThread(()->{});//获取线程idSystem.out.println(thread.getId());//获取线程nameSystem.out.println(thread.getName());}}

2.3 判断线程是否存活

publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newThread(()->{try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}});//false,线程还没启动System.out.println(thread.isAlive()); thread.start();//true,线程正在执行任务System.out.println(thread.isAlive());try{Thread.sleep(2000);}catch(InterruptedException e){thrownewRuntimeException(e);}//false,线程结束System.out.println(thread.isAlive());}}

2.4 前台/后台线程

前台线程:会阻止进程终止,即使主线程执行完毕,只要存在前台线程仍在运行,进程会继续运行直到所有前台线程完成任务。默认情况下,主线程是前台线程,通过Thread类创建的线程默认也是前台线程
后台线程:不会阻止进程终止,当所有前台线程结束时,后台线程会被自动终止,无论是否完成任务
publicclassThreadDemo{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread =newThread(()->{while(true){try{Thread.sleep(1000);System.out.println("Hello Thread");}catch(InterruptedException e){thrownewRuntimeException(e);}}});//把thread设置为后台线程,不会阻止主线程的结束,thread线程会伴随main线程的结束而结束 thread.setDaemon(true); thread.start();System.out.println("Hello Main");}}
运行结果:Hello Main

2.5 详解start和run

start方法:用于启动一个新线程,新线程会执行run方法中的代码。每个线程只能调用一次start,重复调用会抛出IllegalThreadStateException(无论该线程是否执行完任务)
publicclassThreadDemo{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread =newThread(()->{for(int i =0; i <5; i++){System.out.println("Hello Thread");try{Thread.sleep(100);}catch(InterruptedException e){thrownewRuntimeException(e);}}});//开启新线程,thread线程执行run方法 thread.start();for(int i =0; i <5; i++){System.out.println("Hello Main");Thread.sleep(100);}}}
run方法:直接调用run方法不会创建新线程,而是在当前线程中执行run方法的代码
publicclassThreadDemo{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread =newThread(()->{for(int i =0; i <5; i++){System.out.println("Hello Thread");try{Thread.sleep(100);}catch(InterruptedException e){thrownewRuntimeException(e);}}});//main线程执行run方法 thread.run();for(int i =0; i <5; i++){System.out.println("Hello Main");Thread.sleep(100);}}}

2.6 线程休眠

Thread.sleep:是Java中用于暂停当前线程执行的一个静态方法。它允许线程在指定的时间内进入休眠状态,期间不会占用CPU资源。休眠结束后,线程会重新进入就绪(RUNNABLE)状态,等待调度执行
//毫秒级精确度//需要处理InterruptedException(编译时异常)publicstaticnativevoidsleep(long millis)throwsInterruptedException;//纳秒级精确度publicstaticvoidsleep(long millis,int nanos)throwsInterruptedException
知识回顾:main方法中:既可以throws声明异常,也可以try-catch异常

run方法中:只能try-catch异常,因为子类重写父类方法时不能修改父类方法的方法签名

在这里插入图片描述

2.7 currentThread:获取当前线程实例

在继承Thread 类的自定义线程类中,可以通过this关键字直接访问当前线程的属性和方法
classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println(this.getId()+" "+this.getName());}}publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newMyThread(); thread.start();}}
在使用Runnable接口或Lambda表达式创建线程时,无法直接使用this获取线程信息,因为this指向的是Runnable实例而非线程对象。应调用 Thread.currentThread()获取当前线程的引用
publicclassThreadDemo{publicstaticvoidmain(String[] args){Thread thread =newThread(()->{Thread t1 =Thread.currentThread();System.out.println(t1.getId()+" "+ t1.getName());}); thread.start();}}

2.8 变量捕获

1.Java的线程模型:栈帧中的局部变量是线程私有的,这意味着每个线程都有自己的栈帧,局部变量存储在栈帧中,一个线程不得访问另一个线程中的局部变量2.变量捕获(Variable Capture):通常与Lambda表达式和匿名内部类相关,允许捕获外部作用域的变量,但这些变量必须是final修饰或事实final

结合以上两点,变量捕获机制允许一个线程获取另一个线程中的final变量
在上述代码中,thread线程捕获到main线程中的isQuit变量,本质上是将isQuit变量拷贝到thread线程中,而不是直接访问main的栈帧(避免因变量的生命周期而可能产生的线程安全问题)如果允许匿名内部类或lambda表达式访问修改局部变量,就会导致变量逃逸(escape),即变量的引用或值被传递到栈帧之外造成栈中变量的内存地址泄漏,这种情况会引发严重的安全问题,因为原本被Java线程模型规定为线程私有的数据可能会被并发访问

2.9 终止线程

通过修改类变量来终止线程
缺点:类变量属于整个进程的公共变量,访问时需要注意线程安全问题类变量无法唤醒线程中的sleep方法
publicclassThreadDemo{publicstaticboolean isQuit =false;publicstaticvoidmain(String[] args){Thread thread =newThread(()->{while(!isQuit){System.out.println("Hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}System.out.println("Over Thread");}); thread.start();try{Thread.sleep(2000);}catch(InterruptedException e){thrownewRuntimeException(e);} isQuit =true;}}
interrupt():是 Java中用于中断线程的方法。它的核心作用是向目标线程发送一个中断信号,但并不会直接终止线程的执行。需要主动检查中断状态并决定如何响应中断interrupted():检查当前线程的中断状态,并清除中断标志(即调用后中断状态会被重置为false)isInterrupted():检查线程的中断状态,但不会清除中断标志(即调用后不改变中断状态),仅返回当前状态
publicclassTreadDemo{publicstaticvoidmain(String[] args){Thread thread =newThread(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("Hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){ e.printStackTrace();break;}}System.out.println("Over Thread");}); thread.start();try{Thread.sleep(2000);}catch(InterruptedException e){thrownewRuntimeException(e);}//终止线程,并唤醒线程中的sleep thread.interrupt();}}

2.10 join

join方法:用于等待线程执行完成。调用某个线程的join方法后,当前线程会被阻塞,直到目标线程执行完毕
方法说明
join()等待线程无限期执行完成
join(long millis)等待线程执行完成,但最多等待millis毫秒
join(long millis, int nanos)等待线程执行完成,但最多等待millis毫秒+nanos纳秒
publicclassThreadDemo{publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread =newThread(()->{for(int i =0;i <3;i++){System.out.println("Hello Thread");try{Thread.sleep(1000);}catch(InterruptedException e){thrownewRuntimeException(e);}}System.out.println("Over Thread");}); thread.start();//main等待thread线程执行完毕 thread.join();//main线程恢复执行System.out.println("Hello Main");}}
执行结果:
Hello Thread
Hello Thread
Hello Thread
Over Thread
Hello Main

Read more

前端开发中支持跨域的HTML标签和属性(附:前端常见缓存机制跨域性和实际跨域需求总结)

前端开发中支持跨域的HTML标签和属性(附:前端常见缓存机制跨域性和实际跨域需求总结)

本文总结了前端开发中支持跨域的HTML标签和属性。主要包括:支持跨域请求的标签(img、script、link等)及相关CORS属性;跨域通信方式(postMessage、iframe等);CORS属性详解及安全注意事项;常见跨域场景示例(图片加载、表单提交等); 前端常见缓存机制在跨域中的限制总结。 前端常见的跨域需求总结。 文章强调应根据需求选择合适的跨域方案,优先使用CORS等标准化方式,同时注意安全风险控制和浏览器兼容性问题,为开发者提供了全面的跨域解决方案参考。 关联阅读推荐 前端常见缓存方式总结 和 Service Worker 缓存详解 使用 fetch 进行跨域请求 前端开发中可以跨域的HTML标签和属性总结 一、支持跨域请求的HTML标签 标签跨域能力跨域相关属性使用场景CORS要求安全限制<img>✅ 支持跨域加载图片crossorigin加载第三方图片、头像、验证码等设置crossorigin时需CORS无法读取图片内容(除非配置CORS且画布同源)<script>✅ 支持跨域加载JScrossoriginCDN加载JS库、JSONP、模

By Ne0inhk

前端 IndexDB 使用指南

目录 IndexedDB 一句话理解 📊 对比 localStorage 🎯 使用场景(什么时候用?) 💡 使用示例 ⚠️ 注意事项 IDB 完整使用指南 1. 安装与基础操作 安装 四个核心方法 2. 完整工具类封装 3. React 组件中使用 4. 关键概念说明 📌 必知要点 5. 官方资源 6. 故障排除 IndexedDB 一句话理解 浏览器里的大容量本地数据库,比 localStorage 能存更多、更复杂的数据。 📊 对比 localStorage 特性localStorageIndexedDB容量5MB至少250MB,甚至GB级数据类型字符串任何JS对象、文件、二进制查询方式键值对键值、索引、范围查询速度同步(阻塞)异步(不阻塞页面) 🎯 使用场景(什么时候用?) 用 localStorage: * 存

By Ne0inhk
【DGX Spark 实战】部署 vLLM + Open WebUI 运行 Qwen3-Coder-Next-FP8(CUDA 13.0 兼容版)-修订

【DGX Spark 实战】部署 vLLM + Open WebUI 运行 Qwen3-Coder-Next-FP8(CUDA 13.0 兼容版)-修订

感谢Qwen3-Coder-Next-FP8为本文进行润色,调整,绘制架构图。但是所有的文字及链接经过手工修订。需要SGLang推理框架,移步 【DGX Spark 实战】部署SGLang,千问3.5-27B模型初探 我们已严格按您提供的原始内容(包括 CUDA_VERSION=130、CPU_ARCH=aarch64、路径 ~/vllm、用户 admin 等)进行全量修正与标准化,确保所有命令与 DGX Spark 实际环境一致。 摘要本文详细记录在 NVIDIA DGX Spark(Grace Blackwell 架构)上部署 vLLM 推理服务并接入 Open WebUI 的完整流程,包含 FlashAttention 编译、vLLM wheel 安装、Qwen3-Coder-Next-FP8

By Ne0inhk

前后端跨域处理全指南:Java后端+Vue前端完整解决方案

摘要:本文详细介绍跨域问题的产生原因、浏览器同源策略机制,以及基于Java后端和Vue前端技术栈的多种跨域处理方案。涵盖@CrossOrigin注解、全局CORS配置、过滤器、Spring Security集成、Vue代理配置、Nginx反向代理等多种方案,并提供完整可运行的代码示例,适用于初中级开发者学习参考。 一、跨域基础概念 1.1 什么是跨域? 跨域(Cross-Origin)是指浏览器出于安全考虑,限制从一个域(协议+域名+端口)加载的网页去请求另一个域的资源。当协议、域名或端口三者中有任意一项不同时,就会触发跨域限制。 示例: * http://localhost:3000 → http://localhost:8080(端口不同)❌ 跨域 * http://example.com → https://example.com(协议不同)❌ 跨域 * http://api.example.

By Ne0inhk