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

多线程 --- 创建线程与线程的属性

(一).线程的概念1.概念在Java中对线程进程了统一的封装封装成了Thread类2.run方法在Thread中有一个run方法这个方法是一个抽象方法我们需要重写我们的run方法来进行执行run方法是线程的入口方法一旦新的线程启动就要执行这里的代码run方法不需要我们手动调用新的线程创建好了之后自动的取执行注意run方法相当于 “回调函数”3.start方法start方法表示创建了一个新的线程多了一个执行流就意味着在CPU中多了一个人干活所以这个代码就可以“一心两用”start方法才是真正的在系统中创建线程即JVM调用操作系统的API完成线程创建操作注意每个Thread对象只能start一次即每次想要创建一个新的线程都得需要创建一个新的Thread对象(二).线程的创建1.方法1通过继承Thread 类重写run方法class MyThread extends Thread{ Override public void run() { System.out.println(Hello Thread); } }当我将两个线程都写成死循环后程序的运行结果为可以发现程序中的两个线程在交替运行当我使用一个 “休眠”方法 sleep程序再运行sleep方法是一个静态方法表示 “休眠让当前的线程暂时放弃CPU等指定的时间过了之后再执行”直接使用Thread类进行调用即可里面的参数单位为“毫秒”可以看到两个线程在交替执行这说明线程的调度是随机的抢占式执行2.方法2通过实现Runnable接口重写run方法Runnable 表示一个 “可执行任务”通过这个接口来调用run方法这样写可以更好的 “解耦合”将来修改代码的时候更方便class MyRunnable implements Runnable{ Override public void run() { System.out.println(Hello Runnable); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }依旧是两个线程执行的结果还是两者交替执行当我不使用 thread.start()方法而使用thread.run()方法的时候程序的运行结果为可以看出全是 “Hello Runnable”这是因为我调用的是run方法没有创建线程所以只有main这一个线程所以只会执行 run方法里面的内容注意main方法对应的线程即一个进程中至少要包含的那个线程为主线程3.优化方法1使用匿名内部类public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(){ Override public void run() { while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; thread.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } }创建了一个Thread子类子类的名字是匿名的。{ } 里面就可以编写子类的代码即子类中要包含的哪些属性方法以及要重写的父类的方法。创建了这个匿名内部类的实例并将这个实例的引用赋值给了thread当程序运行起来的时候也达到了我们想要的效果注意通过匿名内部类来写一般用于这个代码是 “一次性”的时候4.优化方法2使用匿名内部类public static void main(String[] args) throws InterruptedException { Runnable runnablenew Runnable() { Override public void run() { while (true){ System.out.println(hello runnable); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread threadnew Thread(runnable); thread.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } }和方法2一样这样做的目的是为了“解耦合”。如果直接继承Thread类执行的任务本身和 Thread(线程)这个概念是耦合在一起的我们为了降低耦合是为了后续改代码方便我们只需要记住使用Runnable任务和线程概念是分离的5.优化方法3和方法4使用lambda表达式public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } }对于lambda表达式本质上就是一个 “匿名函数”最主要的用途就是作为 “回调函数”在java 中方法必须要依托于 类 来存在就像lambda表达式就类似于 “函数式接口”创建了一个匿名的函数式接口的子类并且创建出对应的实例并且重写了里面的方法(三).查看线程的工具jconsole.exeJconsole.exe可以看到当前的线程1.找到JDK文件的位置2.根据上图中的路径在此电脑中找到jdk的存放位置3.点击bin文件夹找到jconsole.exe可执行文件4.打开jconsole.exe执行程序5.选择 “不安全的连接”6.选择线程7.Thread-0 和 main 就是我们创建的线程8.查看线程所在的行数线程的调用栈获取线程状态的时刻线程里的代码执行到哪里了(四).Thread类的其他属性和方法1.Thread类的构造方法方法说明Thread()创建线程对象,必须重写Thread类里面的run()方法Thread(Runnable target)使用Runnable对象创建线程对象不需要重写Thread类里面的run()方法只需要重写Runnable类里面的run()方法Thread(String name)创建线程并命名Thread(Runnable target ,String name)使用Runnable对象创建线程对象并命名Thread(ThreadGroup group ,Runnable target)线程可以被用来分组管理分号的组即为线程组 [了解即可]对于Thread(String name)这个构造方法我们可以给线程起名字起什么样的名字都无所谓都不会影响线程的运行起名字的主要目的就是为了 描述线程是干啥的方便调试示例public static void main(String[] args) { Thread thread1new Thread(()-{ while (true){ System.out.println(hello thread1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2new Thread(()-{ while (true){ System.out.println(hello thread2); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread3new Thread(()-{ while (true){ System.out.println(hello thread3); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread1.start(); thread2.start(); thread3.start(); }这是我手动创建了三个线程当程序运行起来的时候三个线程分别抢占式的执行然后通过“jconsole.exe”文件看一下三个线程的运行状态可以看到这就是我们的那三个线程下面我们可以分别对他们进行命名我们再通过 “jconsole.exe”执行文件看一下我们的线程可以发现不管我们是什么类型的名字都可以识别所以说这个命名的主要目的就是为了方便调试注意为什么没有main(主线程)线程在上图中我们并没有发现main线程这是因为我们之前写的代码都是单线程的程序即main方法执行完毕之后程序就结束了但是多线程的程序中当main方法执行完 thread3.start()这一行就直接结束了主线程随即销毁所以在 “jconsole.exe”可执行文件中只能看到3个子线程看不到main线程2.线程的其他属性属性获取方法IDgetId()名称getName()状态getState()优先级getPriority()是否后台线程isDaemon()是否存活isAlive()是否被中断isInterrupted()等待一个线程join()休眠当前线程sleep()(1).getId()类似于PIDJava中给每个运行的线程都分配了id标识线程的身份(2).getName()获取线程的名字(3).isDaemon()想要理解这个方法我们需要先明白什么是后台线程和前台线程如果一个线程的结束不会影响到进程的结束那么这个线程就是 “后台线程”如果一个线程的结束影响到进程的结束此时这个线程就被称为“前台线程”示例public static void main(String[] args) { Thread thread1new Thread(()-{ while (true){ System.out.println(hello thread1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },1线程); Thread thread2new Thread(()-{ while (true){ System.out.println(hello thread2); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },bbb); Thread thread3new Thread(()-{ while (true){ System.out.println(hello thread3); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } },123); System.out.println(thread1.getPriority()); thread1.start(); thread2.start(); thread3.start(); }还是通过这个例子来看在线程图中我们可以看到即使main线程结束了但是线程“123”线程“bbb”线程“1线程”还在既然三个线程还在那么进程就依然存在也就是说这三个线程的存在能够影响到进程继续存在而不能结束此时这三个线程就被称为 “前台线程”剩下的其他线程其他线程都是JVM自带的一些线程他们的存在不会影响到进程的结束即使他们继续存在如果进程结束了那么他们也就结束了此时这些线程就是 “后台线程”例如垃圾回收线程垃圾回收线程跟随整个进程持续执行通过一个例子来看public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); for (int i 0; i 3; i) { System.out.println(hello main); Thread.sleep(1000); } System.out.println(main线程结束); }我们可以发现main线程的结束没有影响到thread线程的结束如果我们想要main线程结束的时候thread线程也结束那么我们就需要将thread线程修改成 “后台线程”我们可以通过setDaemon()方法将线程改成后台线程(4).isAlive()检查系统线程是否存活首先要明确一点Java代码中创建的Thread对象和系统中的线程是一 一对应的关系但是Thread对象的生命周期和系统中的线程的生命周期是不同的可能会存在一种情况就是Thread对象还存活但是系统中的线程已经销毁的情况通俗一点说系统线程 从start()开始到run()执行结束结束之后系统线程就彻底销毁了Thread对象从new开始就没有任何引用指向它直到被垃圾回收之后才算是结束生命周期所以就会出现Thread对象还存活但是对应的系统中的线程已经销毁的情况综上所述isAlive()判断的是对应的系统线程是否还在执行/未终止而不是判断Thread对象本身是否还在内存里。通过一个例子来看public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ for (int i 0; i 3; i) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); System.out.println(thread.isAlive()); // 这个地方一定是false因为这个时候 start 方法还没有执行 thread.start(); while (true){ System.out.println(thread.isAlive()); //isAlive() 判断线程是否存活 Thread.sleep(1000); } }Thread中的代码逻辑3秒之后就结束了所以对应的线程的入口方法里面的逻辑就结束了系统中对应的线程就随之销毁了(在操作系统的角度)但是我们的thread对象依然存在当代码运行起来的时候发现(5).isInterrupted()是否被中断”中断“让一个线程能够结束让线程的入口方法执行完毕线程就随之结束了即 run()方法能够尽快的return()①.示例在理解这个方法之前我们可以先通过一个例子来实现一下 ”中断一个线程“private static boolean isFinishedfalse; public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (!isFinished){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(thread 结束); }); thread.start(); Thread.sleep(3000); isFinishedtrue; }上图中的例子我们是通过修改外部类的成员变量来控制线程然后内部类通过访问外部类的成员来判断是否结束线程问题如果将成员变量isFinished修改成局部变量可以吗通过上面的图片发现报错了这就是我们之前学的变量捕获方面的内容了在lambda里面如果希望使用外面的变量那么就会触发 ”变量捕获“这样的语法首先lambda是“回调函数”执行的时机可能是很久之后了很有可能线程创建好了当前main这里的方法都执行完了那么对应的isFinished就销毁了所以线程thread就无法获取到isFinished了针对于这种情况java就采用了 “变量捕获”的思想把被捕获的变量拷贝一份拷贝给lambda里面外面的变量isFinished是否销毁就不会影响到lambda里面的执行了而拷贝意味着这样的变量就不适合进行修改因为修改一方另一方不会随之改变本质上是两个变量所以最终java这边就不允许进行修改对于引用类型的变量不能修改这个引用指向其他的对象但是引用指向的对象本体是可以进行修改的例如 Test test new Test(); test 就表示引用类型的变量 new Test() 就表示对象的本体上图将局部变量修改成 成员变量就不会涉及到 “变量捕获”的语法了而是转换成“内部类访问外部类的成员” 语法成员变量的生命周期也是让 垃圾回收 来管理的在lambda里面不担心变量生命周期失效的问题也就意味着不需要进行拷贝也不必限制final之类的②.isInterrupted() 和 interrupt()Java的Thread对象中提供了现成的变量来中断线程不需要我们自己进行创建现在将上面的代码进行修改public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (!Thread.currentThread().isInterrupted()){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(thread 线程结束); }); thread.start(); Thread.sleep(3000); System.out.println(main 线程尝试中断 thread线程); thread.interrupt(); }currentThread()方法是一个静态方法所以直接通过类名来进行调用currentThread()方法的作用就是在哪个线程中调用获取到的就是哪个线程的Thread引用当前是在lambda表达式中进行调用的所以返回的结果就是threadisInterrupted()方法是用来判断Thread 里的 boolean 变量的值即判断线程是否被终止注意Thread.currentThread().isInterrupted() 这样写的原因是因为lambda的定义是在Thread实例化之前我们要先重写完run方法之后才能进行对象的实例化所以lambda表达式中是不知道thread的存在的所以我们要先通过Thread.currentThread()方法来获取当前的线程对象interrupt()方法是主动进行终止修改这个 boolean 变量的值从而使调用interrupt()方法的这个线程来结束线程下面就开始运行程序当程序运行起来的时候发现报错了这是为什么这是因为 interrupt()方法除了设置boolean变量之外还会唤醒sleep()这样的阻塞方法在while()循环中大部分的时间都在sleep所以当主线程调用 interrupt()方法的时候极大概率下thread线程正在sleep中此时这个interrupt()方法就会唤醒sleep 从而使sleep()方法抛出异常如何解决这个异常我们可以这样做当抛出的异常的时候不要进行抛出直接break掉这个循环注意这样的效果其实是抛出了异常我们没有进行捕获而是直接中断了程序其实和上面的捕获异常一样同样当抛出异常的时候我们也可以不进行捕获当程序运行起来的时候发现会一直打印 “hello thread”这又是为什么对于上面这个代码是sleep()方法导致的当interrupt()方法将isInterrupted()方法内部修改成true同时将sleep()方法给唤醒了当sleep()方法被唤醒之后将 isInterrupted()方法内部设置回了 false因此在这种情况下如果继续执行循环的条件判定就会发现能够继续执行Java这样实现的好处Java把决定权交给了被终止的线程自己了有三种选择①.直接无视终止指令继续执行。就像catch{}里面什么都没写一样②.直接终止线程直接在{}中加入break或者抛出异常③.当捕获到异常之后可以在处理异常处写上一些代码获取阶段性的结果也就是说如果线程终止了我们可以的到线程终止之后的预期结果而不是随机的结果。(6).join()前面我们介绍过多个线程之间是并发执行随即调度的如果我们不想让多个线程之间随即调度那么我们可以通过join()方法join()方法可以决定多个线程之间的结束的先后顺序如果在main线程中调用thread.join()方法可以让main线程等待thread线程先结束join()等待线程结束join(long millis)等待线程结束最多等 millis 毫秒join(long millis , int nanos)等待线程结束最多等待millis 毫秒 nanos 纳秒 (更精确)示例①.调用不带参数的join()方法public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ for (int i 0; i 3; i) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(thread 线程结束); }); thread.start(); thread.join(); System.out.println(main 线程结束); }当在main线程中调用 thread.join()的时候此时main线程就会等待thread线程先结束当执行到thread.join()的时候main线程就会进入 “阻塞等待”一直等到 thread 线程执行完毕main线程才会继续执行②.调用带一个参数的join()方法public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ for (int i 0; i 3; i) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(thread 线程结束); }); thread.start(); thread.join(1000); System.out.println(main 线程结束); }对于上面的代码可以看出main线程只会等待thread线程1000毫秒如果经过1000毫秒之后thread线程还没有结束那么main线程也就不等了③.调用带两个参数的join()方法public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ for (int i 0; i 3; i) { System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(thread 线程结束); }); thread.start(); thread.join(1000,500); System.out.println(main 线程结束); }带两个参数的join()方法就会使等待的时间更精确但是我们使用的计算机一般很难进行ns级别的精确时间计算。有一类操作系统为 “实时操作系统”可以做到更精确的时间这样的操作系统一般会用于“工业航天”等方面我们日常的系统都不是 “实时操作系统”(7).sleep()休眠当前线程但是注意一点线程是随即调度的所以这个方法只能保证实际休眠时间大于等于参数设置的休眠时间当我们写了 sleep(1000)实际上会休眠比1000多一点因为代码调用sleep相当当前线程于cpu会让出资源当时间到了的时候需要操作系统内核把这个线程重新调用到cpu上才能继续执行也就是说当时间到了的时候意味着允许被调度了而不是立即执行了所以说会比1000多一点特殊写法sleep(0) 写了sleep(0) 意味着让当前的线程 立即放弃cpu资源把cpu让出来给别人更多的执行机会等待操作系统重新调度(8).getState()获取线程的状态站在操作系统的角度进程状态分为 就绪 和 阻塞Java 线程也是对操作系统线程的封装针对线程的状态Java也进行了重新封装进行表示NEW:表示已经创建出来了Thread对象但是还没有startpublic static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ System.out.println(hello thread); }); //获取线程的状态 System.out.println(thread.getState()); }RUNNABLE线程正在CPU上执行/线程随时可以去CPU上执行public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (true){ //什么都不写 //本身也是一段cpu指令一直循环执行也是需要在cpu上运行的 } }); //获取线程的状态 System.out.println(thread.getState()); thread.start(); Thread.sleep(1000); System.out.println(thread.getState()); }TERMINATED内核中的线程已经结束了但是Thread对象还在public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ System.out.println(hello thread); }); //获取线程的状态 System.out.println(thread.getState()); thread.start(); Thread.sleep(1000); System.out.println(thread.getState()); }TIMED_WAITING指定时间阻塞线程阻塞不参与cpu调度不继续执行但是阻塞的时间也是有上限的public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (true){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); Thread.sleep(1000); System.out.println(thread.getState()); }while(true){}中sleep()是2000毫秒然后在主线程中我sleep() 了 1000毫秒所以当 1000毫秒之后我获取thread线程的状态此时thread线程还在阻塞过程中所以状态为TIMED_WAITING但是当2000毫秒之后又会变成RUNNABLE另外 join(时间) 也会进入到 TIMED_WAITING状态public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); thread.join(60000); }WAITING死等和TIMED_WAITING 相对public static void main(String[] args) throws InterruptedException { Thread threadnew Thread(()-{ while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); //只需要调用不带参数的join()方法就可以将状态变成WAITING thread.join(); }BLOCKED是一种由 “锁”导致的阻塞比较特殊等下一章线程安全的时候再进行介绍总结介绍的这几种线程的状态主要是用于调试程序找BUG的时候使用当发现代码中出现BUG的时候①.通过 jconsole.exe 或者 其他工具查看当前的进程中的所有线程找到对应逻辑的线程是谁②.看线程的状态是啥看到TIMED_WAITING /WAITING 怀疑是不是代码中某个方法产生阻塞没有及时唤醒看到BLOCKED怀疑是不是代码中出现了死锁看到RUNNABLE线程本身没问题考虑逻辑上某些条件没有预期触发之类的③.看线程的具体调用栈(尤其是 阻塞的状态线程代码阻塞到哪一行了)

相关文章:

多线程 --- 创建线程与线程的属性

(一).线程的概念1.概念在Java中,对线程进程了统一的封装,封装成了Thread类2.run方法在Thread中,有一个run方法,这个方法是一个抽象方法,我们需要重写我们的run方法来进行执行run方法是线程的入口方法,一旦新…...

会 GIS 开发的不一定会前端开发,但会前端开发的一定要会 GIS 开发,这篇文章给你一次性讲清楚

👉 会 GIS 开发的不一定会前端开发,但会前端开发的一定要会 GIS 开发,这篇文章给你一次性讲清楚1. 问题背景(真实场景) 我见过两类开发者: 一类是 GIS 出身,精通坐标系、空间分析,但…...

Dify + Weaviate + Jina Reranker三引擎联调(仅剩最后2%用户未掌握的混合打分策略)

第一章:Dify Weaviate Jina Reranker三引擎联调全景概览本章呈现 Dify(低代码 LLM 应用编排平台)、Weaviate(向量数据库)与 Jina Reranker(语义重排序模型)协同工作的端到端架构视图。三者分工…...

嵌入式音调生成库:基于GPIO+定时器的方波音乐实现

1. TonePlayer项目概述TonePlayer是一个面向嵌入式系统的轻量级音调生成工具库,专为在压电蜂鸣器(Piezo speaker)上播放8位风格音乐而设计。其核心定位并非通用音频解码器,而是聚焦于资源受限的MCU平台(如STM32F0/F1系…...

拓竹-云安全工程师实习生面经

1. 自我介绍 2. 讲项目经历职责背景什么的 3. 对AI和安全的理解 4. XXS是什么,CSP用来干什么的,怎么配置的,XSS如何防御 5. CORS是什么,会导致什么漏洞,怎么防御 6. SSRF是什么?CDN/DNS绑定用来干什么…...

105【SV】SystemVerilog Interview Questions Set 6

📘 SystemVerilog 面试题集 6 —— 验证工程师的“知识锦囊” 在芯片验证面试中,除了基本概念,面试官更关注你解决实际问题的能力。今天,我们继续解析第六组面试题,涵盖随机化、队列、类继承、竞争避免等实用技巧。每个…...

25年的第二题--旅行最短路径问题

暴力解法思路 弗洛伊德算法全图最短路径搜集有 n 个点, 要每个点都走一遍 枚举所有可能的访问顺序(全排列) 对每种顺序, 按顺序走,算总距离 最后输出最小的总距离//计算任意两个点之间的最短路径!暴力全部计…...

【通信观系列】三十七、卫星物联网

卫星物联网卫星物联网的发展背景卫星物联网的应用价值卫星物联网的技术进展2023-04-10 请大家注意,我说的是“物联网”,而不是“互联网”。 众所周知,按使用对象,互联网可以分为“人联网”和“物联网”。我们普通消费者用户使用…...

PowerBI累计求和实战:从帕累托分析到动态度量值(附完整DAX代码)

PowerBI累计求和实战:从帕累托分析到动态度量值(附完整DAX代码) 在电商数据分析领域,识别关键客户和产品是提升运营效率的核心。当我们需要分析哪些20%的客户贡献了80%的营收时,帕累托分析(80/20法则&#…...

Aipy 代码开发的超强能力

# 伪代码示例:使用aipy进行射电干涉测量数据处理 import aipy import numpy as npdef calibrate_uv_data(uv_file):# 创建UV数据对象uv aipy.miriad.UV(uv_file)# 初始化天线阵列aa aipy.cal.get_aa(mwa, uv[sdf], uv[sfreq], uv[nchan])# 相位校准for pol in [xx…...

罗根口播智能体:IP 口播获客必备神器,罗根智能体实现 IP 口播视频自动化生成

文章标签:# 罗根 #罗根智能体 #罗根口播智能体 #IP 口播智能体 #AI 数字人 #智能 Agent 开发框架 #自媒体口播工具 核心关键词:罗根,罗根智能体,罗根口播智能体,IP 口播智能体 一、罗根智能体核心介绍:轻…...

Chandra OCR入门指南:从HuggingFace加载权重到vLLM推理服务的完整迁移路径

Chandra OCR入门指南:从HuggingFace加载权重到vLLM推理服务的完整迁移路径 如果你手头有一堆扫描的合同、PDF报告、数学试卷或者带表格的文档,想把它们一键转换成结构清晰的Markdown、HTML或JSON,那么Chandra OCR就是你正在寻找的工具。 这…...

基于Simulink的自适应反步法(Adaptive Backstepping)控制​

目录 手把手教你学Simulink——基于Simulink的自适应反步法(Adaptive Backstepping)控制​ 摘要​ 一、背景与挑战​ 1.1 非线性系统控制的痛点​ 1.1.1 未知参数的影响​ 1.1.2 传统反步法的局限​ 1.2 自适应反步法的核心优势​ 1.2.1 原理:参数估计+反步设计融合​…...

ComfyUI-WanVideoWrapper实战指南:8GB显存也能玩转14B AI视频生成模型

ComfyUI-WanVideoWrapper实战指南:8GB显存也能玩转14B AI视频生成模型 【免费下载链接】ComfyUI-WanVideoWrapper 项目地址: https://gitcode.com/GitHub_Trending/co/ComfyUI-WanVideoWrapper 还在为AI视频生成的高显存门槛而苦恼吗?每次尝试运…...

AI4S应用:药物研发中结合自由能计算方法的创新突破

▊ 药物研发中结合自由能计算应用现状 药物分子通过对靶蛋白的识别与结合作用,能够调控靶蛋白功能,进而实现治疗疾病的效果。蛋白质的许多关键生理和药理活动是通过与小分子相互作用来实现,比如酶的催化特性是由其与底物的相互作用所体现的。…...

图文搜索不准?立知lychee-rerank-mm快速部署,精准排序搜索结果

图文搜索不准?立知lychee-rerank-mm快速部署,精准排序搜索结果 1. 为什么需要多模态重排序 在日常使用搜索引擎或内容平台时,我们经常会遇到这样的困扰:明明输入了精确的查询词,返回的结果却总是差强人意。比如搜索&…...

W7500裸机HTTP服务器:基于W5500硬件协议栈的嵌入式LED控制

1. 项目概述httpServer是为 WIZwiki-W7500 开发板定制的轻量级嵌入式 HTTP 服务器示例程序,其核心目标并非构建通用 Web 服务框架,而是以最小资源开销实现对硬件外设(特别是板载 LED)的远程状态控制与交互。该程序直接运行于 W750…...

LIS302加速度传感器SPI驱动开发与嵌入式集成

1. LIS302加速度传感器驱动库深度解析:面向嵌入式系统的SPI接口实现LIS302系列是意法半导体(STMicroelectronics)推出的超低功耗、三轴数字加速度传感器,广泛应用于便携式设备的姿态检测、振动监测、跌落保护及运动识别等场景。该…...

解锁《原神》60帧限制:从硬件封印到视觉自由的进阶指南

解锁《原神》60帧限制:从硬件封印到视觉自由的进阶指南 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 你是否曾为《原神》那恒定的60帧限制感到困扰?当你的高端显…...

PyTorch Geometric安装避坑大全:从版本地狱到一键成功,我总结了这份Win/Mac/Linux三平台检查清单

PyTorch Geometric跨平台安装终极指南:从版本陷阱到系统级验证 第一次尝试安装PyTorch Geometric(PyG)时,我花了整整两天时间在版本冲突和依赖地狱中挣扎。那些undefined symbol错误和CUDA版本不匹配的报错信息,至今想…...

GDAL3.1.2+VS2015编译指南:如何用CMake搞定PROJ6依赖?附现成编译好的lib文件

GDAL 3.1.2与VS2015深度编译实战:CMake可视化配置与PROJ6依赖全解析 在空间数据处理领域,GDAL作为地理信息系统的"瑞士军刀",其重要性不言而喻。但对于需要在Windows平台下进行二次开发的科研人员来说,从源码编译GDAL往…...

从理论到实践:TimeGAN驱动的时间序列场景生成与多维可视化解析

1. TimeGAN:时间序列生成的革命性突破 第一次接触TimeGAN是在处理一组电力负荷预测数据时遇到的难题——我们只有少量历史数据,却需要模拟未来可能出现的各种用电场景。传统方法要么需要复杂的参数假设,要么生成的序列缺乏时间依赖性。直到发…...

嵌入式轻量级软件定时器:基于时间轮的毫秒级超时管理

1. 项目概述SimpleSoftTimer 是一个面向资源受限嵌入式系统的轻量级纯软件定时器实现,其设计哲学直指嵌入式开发中最频繁也最易出错的场景之一:超时控制。它不依赖硬件定时器外设(如 TIMx)、不引入 RTOS 内核调度机制(…...

C++高并发内存池:内存池调优与测试

前面我们已经完成了三种Cache的设计。本期我们就来调整一下内存池相关的设计问题 相关代码在我的个人gitee:高并发内存池: 个人学习的项目——高并发内存池 目录 对于大于256KB的内存申请释放 释放对象优化 配备内存池申请变量 多线程下与malloc的性能测试对比…...

Youtu-Parsing助力AI编程:自动解析技术文档生成代码片段

Youtu-Parsing助力AI编程:自动解析技术文档生成代码片段 每次接触一个新的开发库或者框架,你是不是也经历过这样的时刻?面对动辄几十页的官方文档,或者一个结构复杂的开源项目README,感觉无从下手。想快速写个Demo试试…...

Troyka-IMU库详解:10-DOF惯性测量单元Arduino驱动开发

1. Troyka-IMU 库深度解析:面向嵌入式工程师的 Amperka 10-DOF 惯性测量单元驱动开发指南1.1 项目定位与工程价值Troyka-IMU 是专为 Amperka 公司推出的10 自由度(10-DOF)惯性测量单元模块设计的 Arduino 兼容库。该模块集成四类高精度传感器…...

从零搭建CarSim与Simulink联合仿真环境:实现定速巡航控制

1. 环境准备与软件安装 第一次接触CarSim和Simulink联合仿真时,我被各种专业术语搞得晕头转向。后来才发现,只要把这两个软件想象成一对默契的搭档——CarSim负责模拟真实车辆行为,Simulink则扮演控制大脑的角色。搭建环境就像组装乐高积木&a…...

无障碍辅助先锋:OpenClaw+QwQ-32B语音控制电脑全流程实测

无障碍辅助先锋:OpenClawQwQ-32B语音控制电脑全流程实测 1. 为什么我们需要语音控制电脑 去年冬天,我的一位因脊髓损伤而行动不便的朋友向我倾诉了他的困扰——每天需要花费大量时间在简单的电脑操作上。一个简单的网页搜索可能要耗费他十几分钟&#…...

中小企业NLP提效方案:MT5中文数据增强镜像在训练集扩增中的落地实践

中小企业NLP提效方案:MT5中文数据增强镜像在训练集扩增中的落地实践 你是不是也遇到过这样的困境?公司想做一个智能客服或者文本分类系统,但手头只有几百条标注数据,模型训练出来效果总是不尽人意。找外包公司标注?成…...

Visual Studio Code 远程开发:调试 Pixel Mind Decoder 调用代码

Visual Studio Code 远程开发:调试 Pixel Mind Decoder 调用代码 1. 前言:为什么需要远程开发 当你需要在GPU服务器上运行和调试AI模型代码时,直接在本地开发会遇到各种环境问题。Visual Studio Code的远程开发功能可以让你像在本地一样编写…...