Java多线程编程(初阶)
引言关于线程的概念想必尊敬的读者已经有所了解不然也不会点开这篇博客所以基础概念等不再做多余赘述这篇博客仅仅是记录自己在学习多线程编程的途中的学习所得有写的不好的地方还望海涵。初识线程第一道多线程程序在Java中提供了Thread类来帮助我们实现多线程编程我们在学习之前可以先通过观察一段简单的多线程代码来看看多线程编程的运行效果class MyThread extends Thread{ Override public void run(){ while(true){ System.out.println(手动创建的线程在运行); } } } public class demo01{ public static void main(String[] args) { Thread t new MyThread(); t.start(); while(true){ System.out.println(主函数线程在运行); } } }如果你第一次见到它可能会觉得不可思议程序里明明有两个死循环一个在run里一个在main里按常理代码应该先卡在第一个while(true)里永远出不来才对。可运行之后控制台却疯狂地交替刷出两句话主函数线程在运行手动创建的线程在运行主函数线程在运行手动创建的线程在运行……就好像两个人同时抢着向你喊话互不相让那么恭喜你就在这一刻你亲手写出了自己的第一个多线程程序。这个开场之所以“神奇”全在于t.start()。它悄悄做了一件事让 Java 虚拟机在背后又开辟了一条新的执行路径也就是一个新的线程。新线程会去执行run方法里的死循环而原来的main线程呢根本没停继续往下执行自己的那个死循环。于是两个while(true)真的就在“同时”跑了——至少在宏观上它们交替执行谁也没有被谁堵死。这就是多线程编程带给我们的第一份礼物从顺序执行到并行运转。其实观察仔细并且认真思考的话还是会有一些疑问的我们明明把要运行的程序是封装在MyThread类的run方法里的那为什么在main里面要通过start方法去运行呢为了弄懂二者的区别我们可以通过对比运行的结果来观察差异class MyThread extends Thread{ Override public void run(){ while(true){ System.out.println(手动创建的线程在运行); } } } public class demo02{ public static void main(String[] args) { Thread t new MyThread(); t.run(); while(true){ System.out.println(主函数线程在运行); } } }输出结果如下手动创建的线程在运行手动创建的线程在运行手动创建的线程在运行手动创建的线程在运行......可以看出如果是执行run方法的话主函数启动后会进入run方法然后一直运行run方法里面的死循环没有运行属于main里面的死循环这也违背了我们多线程程序并发运行的初衷在直接调用run方法的时候其实就和普通的调用类的方法没有区别我们的程序会老老实实的运行这个方法里面的内容直到这个方法运行结束才会退出在这个过程中没有任何新的线程产生多线程编程更是无从谈起。那么我来看看start为什么能实现多线程运行。在我们调用start的时候JVM会干这两件事1.JVM会调用操作系统的API在操作系统中创建一个新的线程并添加进线程运行列表2.新线程自动去调用这个对象的run方法于是原线程如 main 线程在 start() 返回后立刻继续往下执行新线程则并发地跑 run() 里的代码。两个 while(true) 就这样被分配到了两条独立的线程上宏观上同时运行实现了我们多线程运行的目的。Thread的多种初始化方式1.继承Thread类其实这种初始化方法就是我们在第一道多线程程序里的方法我们通过继承Thread类并重写父类的run方法来运行自己想要运行的程序从而实现多线程编程class MyThread extends Thread{ Override public void run(){ //新建线程中的程序 } } public class demo03{ public static void main(String[] args) { Thread t new MyThread(); t.start(); .... //main里面的代码 } }2.实现Runnable接口传参除了直接继承Thread类,我们还可以通过实现Runnable接口然后创建一个对应的对象传参给一个普通的Thread类的方式来实现一个线程的创建。用实现Runnable接口的方法创建线程可以解耦合提升代码的效率。class MyRunnable implements Runnable{ Override public void run(){ //...... } } public class demo04 { public static void main(String[] args) { MyRunnable r new MyRunnable(); Thread t new Thread(r); t.start();//开始执行t线程中重写的run方法中的代码 //.......main函数中的代码 } }3.匿名内部类初始化Thread如果仔细观察上面的两种初始化方式我们可以发现有一个共同点它们的核心都是重写run函数那么我们可以使用匿名内部类的方式初始化大大减少代码的篇幅。public class demo05 { public static void main(String[] args){ Thread t new Thread(){ Override public void run(){ //...... } };//采用匿名内部类的方式重写run方法 t.start(); //...... } }4.匿名内部类初始化Runnable接口传参初始化Thread和第3种初始化方式同理我们也可以采用匿名内部类的方式实现Runnable接口传参进行初始化。public class demo06 { public static void main(String[] args) { Thread t new Thread(new Runnable() { Override public void run() { //...... } }); t.start(); } }5.Lambda表达式初始化Thread既然匿名内部类可以初始化Thread匿名我们可以用Lambda表达式来进一步降低代码的复杂度先用lambda表达式实现一个Runnable再把Runnable传给Thread进行初始化。public class demo07 { static void main(String[] args) { Thread t new Thread(()-{ //...... }); t.start(); //...... } }6.Lambda表达式单独实现Runnable传入如果想提高代码的复用性我们也可以先单独实现一个Runnable然后再把实现好的Runnable传入Thread进行初始化。public class demo08 { static void main(String[] args) { Runnable r ()-{ //...... }; Thread t1 new Thread(r); Thread t2 new Thread(r); t1.start(); t2.start();//其它Thread对象也可以复用已经实现好的Runnable //...... } }Thread的其它参数的初始化上面的6种初始化方式主要是讲如何创建一个线程Thread其实支持给线程进行命名在创建Thread对象的时候我们就可以通过传参的方式对创建的Thread对象进行命名。public class demo09 { static void main(String[] args) { Thread t1 new Thread(()-{ while(true){} }); Thread t2 new Thread(()-{ while(true){} }); Thread t3 new Thread(()-{ while(true){} }); Thread t4 new Thread(()-{ while(true){} }); Thread t5 new Thread(()-{ while(true){} }); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }在上面这组代码中我创建了5个未命名的线程并启动在Java自带的监视窗口中下图我们可以看到系统会从Thread-0开始默认命名在多线程的程序中我们如果想监视线程的运行情况这样肯定是不方便的所以有了可以进行命名的初始化方式。Thread的传参有以下几种Thread() //创建线程对象Thread(Runnnable r) //使用Runnable对象创建线程对象Thread(String name) //创建线程对象并命名Thread(Runnable r,String name) // 使用Runnable对象创建线程对象并命名我们这里以第四种为例演示一下效果public class demo10 { static void main(String[] args) { Thread t1 new Thread(()-{ while(true){} },运行t1线程); Thread t2 new Thread(()-{ while(true){} },运行t2线程); Thread t3 new Thread(()-{ while(true){} },运行t3线程); Thread t4 new Thread(()-{ while(true){} },运行t4线程); Thread t5 new Thread(()-{ while(true){} },运行t5线程); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }在线程运行的监视窗口可以看到原来的默认命名变成了我们自己的命名在多线程程序中我们可以通过描述该线程的作用进行命名来更好的了解程序的运行。多线程程序的演示sleep在下面这段代码中我们从运行结果可以看到多线程程序在运行期间每个线程的运行时间是随机的public class demo11 { static void main(String[] args) { Thread t new Thread(()-{ while(true){ System.out.println(t线程在运行); } }); t.start(); while(true){ System.out.println(main方法的线程在运行); } } }运行结果截图我们可以通过调用Thread的静态方法sleep并设置时间来人为控制一下线程在CPU上运行的时间public class demo12 { static void main(String[] args) { Thread t new Thread(()-{ while(true){ System.out.println(t线程在运行); try { Thread.sleep(1000);//这里将时间设置成1000毫秒 } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); while(true){ System.out.println(main方法的线程在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }运行结果截图关于调用sleep方法的抛出异常的补充说明可以看到在上述代码中如果不对调用了的sleep方法进行异常处理代码是会报错的但其实在重写的run方法里和main方法里的处理异常的方式页不同我们来看看区别可以看到main方法中的sleep的异常处理是有两种方法的一种是try-catch捕获还有一种是在main方法前用throws声明但是在重写的run方法里只支持try-catch捕获却没有支持throws声明这是为什么呢我可以尝试着都使用throws声明看看会发生什么可以看到同样是使用throws声明main方法那里是正常的但是run方法却报错了我们来看看报错原因可以看到IDEA告诉我们我们重写的run方法是继承自父类Thread的run方法而Thread中的run方法并没有抛出InterruptedException这个异常所以子类也不能抛出因为子类如果抛出异常而父类没有抛出那么子类的run的方法签名和父类的run方法签名不一致就不构成子类对父类的方法重写了所以继承了Thread的子类中run方法使用sleep方法只能使用try-catch的方式处理异常。多个线程运行效果演示在多线程运行程序中除了线程占据CPU的时长是随机的CPU调度线程的次序在不做设计的情况下也是随机的我们可以通过下面这组代码的运行结果来观察。public class demo13 { static void main(String[] args) { Thread t1 new Thread(()-{ while(true){ System.out.println(t1线程在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread t2 new Thread(()-{ while(true){ System.out.println(t2线程在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread t3 new Thread(()-{ while(true){ System.out.println(t3线程在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); Thread t4 new Thread(()-{ while(true){ System.out.println(t4线程在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t1.start(); t2.start(); t3.start(); t4.start(); while(true){ System.out.println(main线程在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }运行结果截图Thread的常见属性1.是否后台线程我们先了解什么叫后台线程什么叫前台线程什么叫后台线程。在学习多线程程序以前我们总是认为main方法执行完毕之后程序就运行结束了但学习了多线程编程之后我们会发现完全不是这么回事比如下面这段代码就可以看出main方法的执行完毕并不等于程序运行的结束public class test { private static Thread THread; public static void main(String[] args) throws InterruptedException { Thread t new Thread(()-{ for(int i0;i5;i){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(t 线程运行结束); }); t.start(); Thread.sleep(1000); System.out.println(main 线程运行结束); } }运行结果如下可以看出main线程里的代码运行结束后t线程的代码仍在运行t线程运行结束后程序才完全结束由此可以看出程序的结束与否不取决于main方法里所有代码执行完毕。实际上程序的结束与否取决于进程中是否还存在前台线程。我们默认创建的线程都是前台线程而JVM有一条铁律只要还有任何一个前台线程没有结束整个进程就不会退出JVM 会一直等它。所以main方法中的代码执行结束后程序仍没有停止而是把同样是前台线程的t线程也运行结束才结束。而后台线程也被称为“守护线程”它的职责是在后台默默提供一些辅助服务比如垃圾回收、缓存清理等如下图中除了我们创建的t线程(Thread-0)和main线程外还有很多其它的线程这些都是自动创建的后台线程。JVM 不会关心后台线程有没有执行完当前台线程全部结束时JVM 会直接退出并且强制终止所有还在运行的后台线程。我们可以调用setDaemon(true)这个方法把前台线程设置为后台线程注意这个方法必须在调用start方法前调用不然会抛出IllegalThreadStateException。我们通过这个方法来看看前后台线程的性质public class test { private static Thread THread; public static void main(String[] args) throws InterruptedException { Thread t new Thread(()-{ for(int i0;i5;i){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(t线程运行结束); }); t.setDaemon(true); t.start(); System.out.println(main线程运行结束); } }运行结果如下图可以看到本该输出5次的hello thread由于被设置成了后台线程在main方法里的代码被执行完后随着main线程的结束程序里的所有前台线程都结束了身为后台线程的t线程也被强制终止了没有被执行完。而想要知道一个线程是否是后台线程可以用isDaemon()这个方法其返回类型是booleanpublic class test { public static void main(String[] args) throws InterruptedException { Thread t new Thread(); System.out.println(t.isDaemon()); // false默认是前台线程 t.setDaemon(true); System.out.println(t.isDaemon());// true现在变成后台线程了 } }运行结果如下图2.线程名称在线程初始化的时候我们提到了线程在初始化的时候可以指定命名未指定的时候系统也会进行默认命名我们可以用getName()方法来获取线程的名字public class test { public static void main(String[] args) throws InterruptedException { Thread t1 new Thread();//系统默认命名 Thread t2 new Thread(自命名线程);//自己命名的线程 System.out.println(t1.getName()); System.out.println(t2.getName()); } }线程的各种属性以及接口方法属性获取方法说明线程IDlong getId()线程的唯一标识符由 JVM 自动分配从 1 开始递增线程终止后 ID 不会被重用。线程名称String getName()线程的名字可在创建时通过构造函数指定也可通过setName(String)修改。默认名称如Thread-0。优先级int getPriority()线程调度优先级范围是Thread.MIN_PRIORITY(1)~Thread.MAX_PRIORITY(10)默认通常为Thread.NORM_PRIORITY(5)。优先级高只是“建议”调度器多分配时间片不保证一定优先执行。线程状态Thread.State getState()返回当前线程的状态值为枚举NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED。用于判断线程的生命周期阶段。是否存活boolean isAlive()线程已启动且尚未终止时返回true。即从start()调用后到run()结束之间为存活状态。是否守护线程boolean isDaemon()返回线程是前台线程false还是后台守护线程true。可通过setDaemon(true)设置但必须在start()之前调用。是否被中断boolean isInterrupted()检查该线程的中断标志位是否为true不清除标志位。常用于循环条件判断线程是否被请求停止。线程组ThreadGroup getThreadGroup()返回该线程所属的线程组常用于批量管理线程。默认情况下新建线程属于创建它的线程的线程组。中断一个线程在讲完线程的创建、启动和基本属性后我们终于要面对一个关键问题线程不可能永远跑下去我们该如何让它停下来如果是单线程程序代码顺序执行完自然就结束了。但多线程里一个线程往往在循环里干活或者正睡着觉等待唤醒。强行把它“杀”掉不仅粗暴还可能让共享资源处于一团乱麻的状态。所以Java 选择了一种更优雅、也更符合程序员思维的方式——中断机制。我们有两种方式来中断线程在讲Java的interrupt()方法之前我们可以先了解共享标记的方式。1.通过共享标记进行沟通我们可以通过设置一个全局变量作为共享标记通过这个标记来控制线程的运行情况public class test { private static boolean running true; public static void main(String[] args) { Thread t new Thread(()-{ while(running){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(t线程退出); }); t.start(); Scanner sc new Scanner(System.in); System.out.println(请输入整数0表示让线程T终止); int n sc.nextInt(); if(n0){ running false; } } }在上面这段代码中我们设置了一个boolean类型的全局变量running来控制线程的运行当我们输入0的时候running会变成false重写的run方法里的while循环结束run方法中的代码执行完毕进而控制我们线程运行的中断。但是上述方法不是真正意义上的中断线程本质上仍然是线程运行的“结束”而不是中断比如如果是下面这样我们把每次println后的sleep时间设置的很长public class test { private static boolean running true; public static void main(String[] args) { Thread t new Thread(()-{ while(running){ System.out.println(hello thread); try { Thread.sleep(100_000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(t线程退出); }); t.start(); Scanner sc new Scanner(System.in); System.out.println(请输入整数0表示让线程T终止); int n sc.nextInt(); if(n0){ running false; } } }执行完sleep()后线程会进入阻塞状态即使我们输入了0该线程也会等sleep()中的时间结束后才会进入到while循环条件的判断并结束该循环进而结束线程而不是我们想要的立刻中断这个线程想要真正的中断一个线程我们还是要依靠Java的interrupt()方法。2.调用interrupt()方法我们可以通过的java的interrupt()方法来中断一个线程在重写的run方法的while里我们就可以先通过当前线程后使用isInterrupt()方法来判断该线程是否处于中断状态来作为循环的判断条件public class test { public static boolean running true; public static void main(String[] args){ Thread t new Thread(()-{ Thread cur Thread.currentThread();//获取当前线程 while(!cur.isInterrupted()){ System.out.println(t线程正在运行); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); Scanner in new Scanner(System.in); int num in.nextInt(); System.out.println(输入0终止线程); if(num 0){ t.interrupt(); } } }相较于上一种方法即使线程在调用sleep()时处于阻塞状态这时候给它发中断信号Java 也会强行把线程从阻塞中唤醒并且抛出一个InterruptException。补充知识点基础语法相关running能不能是局部变量在中断线程的第一种方法中我们把running设置成了全局的静态变量不妨设置成局部变量再试试效果如图所示当我们把共享标记running设置成全局变量的时候线程t的Lambda表达式内部报错了我们可以先看一下IDEA给出的报错原因在报错提示中说了lambda表达式中捕获的变量必须是final或者事实final事实final是指这个变量没有被final修饰但是也没有修改在我们上述代码中running既不是final没有final修饰也不是事实final后续在main方法的if判断里面有被修改,不符合lambda的变量捕获的要求所以没法通过编译。那为什么这里的变量捕获要求变量必须是final或者事实final呢试想一下如果lambda表达式中使用的变量在lambda表达式执行之前就被main方法释放掉了那还怎么访问呢所以java中的变量捕获事实上是把使用的变量的值“拷贝”了一份那么就有了新问题这个值是拷贝来的如果这个变量后续的值被修改了由于是在lambda外部所以lambda内部无法同步更新修改就会导致变量的值不对等所以lambda在变量捕获的时候会要求捕获的变量是final或者事实final。其实如果仔细的话这时候就会发现一个隐藏的新问题了我最开始设置的running也不是final或者事实final那为什么可以正常运行呢这就涉及到lambda对静态变量的捕获了刚刚提到lambda捕获局部变量时是“拷贝”它的值但是lambda在捕获静态变量时获取的是静态变量的内存引用地址它每次执行都会去主内存中读取当前最新的值所以编译器不要求静态变量是不可变的。

相关新闻

【项目编号project16578】|SpringBoot智慧小区服务管理系统:物业报修、公告缴费与社区服务一体化

【项目编号project16578】|SpringBoot智慧小区服务管理系统:物业报修、公告缴费与社区服务一体化

技术栈:SpringBoot MySQL摘要:智慧小区服务管理系统适合围绕物业服务闭环展开,包括业主信息、报修处理、公告发布、缴费记录和后台管理等模块。关键词:project16578、智慧小区服务管理系统、SpringBoot、项目实战一、项目概述智慧…

2026/7/23 20:57:24阅读更多 →
MRAM存储器使用方法(如何使用MRAM提高边缘计算)

MRAM存储器使用方法(如何使用MRAM提高边缘计算)

在边缘计算高速发展的当下,终端设备对存储器件的延迟、功耗、稳定性与耐用性提出了严苛要求,传统存储方案已难以适配场景需求。MRAM存储器作为新一代非易失性磁性随机存取存储器件,凭借独特的物理存储架构,可实现全域随机读写操作…

2026/7/23 20:57:24阅读更多 →
AI原生应用中的函数调用技术解析与实践

AI原生应用中的函数调用技术解析与实践

1. AI原生应用中的函数调用革命去年我在开发一个智能客服系统时,第一次真正体会到AI原生应用的威力。传统开发中,我们需要为每个业务场景编写大量if-else逻辑,而采用函数调用机制后,大模型能够自主判断何时调用哪些API&#xff0c…

2026/7/23 20:55:23阅读更多 →
谁还在硬啃组态屏?我直接喂饭

谁还在硬啃组态屏?我直接喂饭

玩组态屏这事吧,说难不难,说简单也真有不少坑。刚入坑那会儿我也是一路踩着雷过来的,今天把几个核心知识点捋一捋,给刚上手的兄弟们省点弯路。先聊选型。很多人上来就盯着尺寸看,7寸还是10寸,其实比尺寸更重…

2026/7/23 22:23:39阅读更多 →
C语言学习(跟b站鹏哥)

C语言学习(跟b站鹏哥)

下面开始C语言的学习,总结鹏哥C语言重点方便快速回顾复习。适用人员(小白学习c语言,考研408学习C语言,z站学习鹏哥c语言)第二课由C语言编写的为.c文件,需要先进行编译,然后进行链接成为可执行文…

2026/7/23 22:23:39阅读更多 →
新 e 选烤火罩防水防污吗,好不好清洁?

新 e 选烤火罩防水防污吗,好不好清洁?

外层三防 PU 材质,汤水油渍一擦即净,不用频繁拆洗,冬天打理省心,不会缩水变形。

2026/7/23 22:23:39阅读更多 →
新手写小说少走 90% 的弯路:AI 工具正确用法,从选题到投稿全流程指南

新手写小说少走 90% 的弯路:AI 工具正确用法,从选题到投稿全流程指南

很多人心里都藏着一个写小说的念头,但真正能写完、能签约的人少之又少。不是没有才华,而是新手太容易踩进各种误区:题材选不对,大纲写不好,开头留不住人,写着写着就跑偏,最后折腾半天没成果&…

2026/7/23 22:23:39阅读更多 →
ChatGPT、Codex、Plus与Pro:AI状态工程为什么总丢进度?

ChatGPT、Codex、Plus与Pro:AI状态工程为什么总丢进度?

很多开发者在使用ChatGPT和Codex处理长任务时,都会遇到一种看似矛盾的情况。对话记录还在。 项目文件也已经读取。 前面的分析过程没有丢失。 AI甚至能够复述最初的需求。但继续执行时,它却可能出现这些问题:重复完成已经做过的步骤&#xff…

2026/7/23 22:23:39阅读更多 →
从传统架构到超融合:深圳联众合18年实战经验,助企业构建稳定、安全、高效的IT基础设施

从传统架构到超融合:深圳联众合18年实战经验,助企业构建稳定、安全、高效的IT基础设施

很多企业的 IT 负责人都有过这样的经历:半夜接到报警电话,核心 ERP 系统因为单点故障宕机,生产线被迫停摆;或者是办公电脑老化严重,维护一台就要花半天时间,员工抱怨连连。更让人头疼的是,随着数…

2026/7/23 22:21:39阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/23 0:56:31阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/23 0:56:31阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 0:56:31阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:00:28阅读更多 →
从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:28阅读更多 →
油泥处理设备哪里能买到

油泥处理设备哪里能买到

油泥处理设备哪里有?这是许多从事油田、炼化、清罐业务的从业者最关心的问题。根据河南三丰环保设备有限公司的行业经验,选购油泥处理设备的核心在于设备能否适配当地环保法规与原料特性,而非单纯看价格。该公司总经理王钦田先生指出&#xf…

2026/7/23 0:00:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/22 22:56:18阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/23 18:58:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/23 18:58:18阅读更多 →