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

【VLM】Qwen3-VL模型架构和训练流程

【VLM】Qwen3-VL模型架构和训练流程

note * Qwen3-VL模型,提供稠密型(2B/4B/8B/32B)和混合专家型(30B-A3B/235B-A22B)两种变体。通过集成高质量的多元模态数据迭代和架构创新(如增强的交错MRoPE、DeepStack视觉-语言对齐和基于文本的时间对齐) * 其原生支持256K token的交错序列,使其能够在长复杂文档、图像序列和视频上进行稳健的推理,特别适用于现实世界应用中高保真跨模态理解的需求。Qwen3-VL系列的密集和MoE变体确保了在不同延迟和质量要求下的灵活部署,后训练策略包括非思考模式和思考模式,进一步提升了模型的应用范围。 * 数据过滤方面,去除噪声、低对齐样本,确保数据质量与多样性。 * 模型架构方面,使用DeepStack 跨层融合,提取视觉编码器多中间层特征,通过轻量残差连接注入 LLM 对应层,强化视觉-语言对齐,保留从低级到高级的丰富视觉信息。 * RoPE旋转位置编码的高低频含义: * 低频:转得慢,擅长远距离位置区分(长序列、大图、长视频等) * 高频:转得快,位置稍微一变,角度就剧变,擅长近距离精细区分(小区域、局部细

By Ne0inhk
Docker 安装 OpenClaw 报错排查:如何解决Gateway auth is set to token, but no token is configured``Missing config

Docker 安装 OpenClaw 报错排查:如何解决Gateway auth is set to token, but no token is configured``Missing config

Docker 安装 OpenClaw 报错排查:如何解决Gateway auth is set to token, but no token is configured``Missing config. Run openclaw setup``control ui requires HTTPS or localhost``Proxy headers detected from untrusted address 按错误关键词 Ctrl+F 秒搜定位,建议收藏备用! 文章目录 * Docker 安装 OpenClaw 报错排查:如何解决`Gateway auth is set to token, but

By Ne0inhk
SpringAI 大模型应用开发篇-SpringAI 项目的新手入门知识

SpringAI 大模型应用开发篇-SpringAI 项目的新手入门知识

🔥博客主页: 【小扳_-ZEEKLOG博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 SpringAI 概述         1.1 大模型的使用         2.0 SpringAI 新手入门         2.1 配置 pom.xml 文件         2.2 配置 application.yaml 文件         2.3 配置 ChatClient         2.4 同步调用         2.5 流式调用         2.6 System 设定         2.7 日志功能         2.8 会话记忆功能

By Ne0inhk