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

【Java EE】多线程-初阶-Thread 类及常见方法

多线程-初阶

  • 2. Thread 类及常⻅⽅法
    • 2.1 Thread 的常⻅构造⽅法
    • 2.2 Thread 的⼏个常⻅属性
    • 2.3 启动⼀个线程 - start()
    • 2.4 中断⼀个线程
    • 2.5 等待⼀个线程 - join()
    • 2.6 获取当前线程引⽤
    • 2.7 休眠当前线程

本节⽬标
• 认识多线程
• 掌握多线程程序的编写
• 掌握多线程的状态
• 掌握什么是线程不安全及解决思路
• 掌握 synchronized、volatile 关键字

2. Thread 类及常⻅⽅法

Thread 类是 JVM ⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。
⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,类似下图所⽰,⽽Thread 类的对象就是⽤来描述⼀个线程执⾏流的,JVM 会将这些 Thread 对象组织起来,⽤于线程调度,线程管理。
在这里插入图片描述

2.1 Thread 的常⻅构造⽅法

在这里插入图片描述
Thread():使用这个写法,必须要重写 Thread 的 run
Thread(Runnable target):此时不要重写 Thread 的 run
在这里插入图片描述

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

在这里插入图片描述
这里没有看到 main 线程。一个进程启动,肯定得现有 main 线程调用 main 方法
注意!! 此处不是 main 线程没有被创建,而是执行太快,执行完毕了!!
在这里插入图片描述

2.2 Thread 的⼏个常⻅属性

在这里插入图片描述
• ID 是线程的唯⼀标识,不同线程不会重复。(java 代码无法获取到 pcb 中的 id)这里的 id 和 系统中 pcb 上的 id 是不同的,是 jvm 自己搞的一套 id 体系
• 名称是各种调试⼯具⽤到
• 状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
• 优先级⾼的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏
• 是否存活,即简单的理解,为 run ⽅法是否运⾏结束了
• 线程的中断问题,下⾯我们进⼀步说明

public class ThreadDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName()  + ": 状态: " + thread.getState());}
}

getId():jvm 自动分配的身份标识,会保证唯一性
getState():进程有状态:就绪状态,阻塞状态。线程也有状态,Java 中对线程的状态,又进行了进一步的区分(比系原生的状态,更丰富一些)
getPriority():线程的优先级 在 java 中,设置优先级,效果不是很明显(对内核调度器的调度过程产生一些影响) 说了不算,算了不说,即使你手动给某个线程设置一个非常高的优先级,实际运行效果不一定明显(线程调度是操作系统完成,系统随机调度)
isDaemon():daemon 一一>守护~ 是否是守护线程 (非常抽象) 也可以叫做,是否是"后台线程"。操作系统中是有守护进程概念的(也可以理解成 后台进程 )。前台线程的运行,会阻止进程结束。后台线程的运行,不会阻止进程结束

咱们自己代码创建的线程,包括 main 主线程默认都是前台线程,可以通过 setDaemon 方法来修改!
什么情况要设置为后台线程呢?一一> 我不期望这个线程影响进程结束
比如,有的线程负责进行 gc(垃圾回收),gc 是要周期性持续性执行的.不可能主动结束,要是把他设为前台,进程就永远也结束不了了
要不要有后台线程都是看实际需求
在 jconsole 中看到的 jvm 中包含一些其他的内置的线程, 就属于后台线程了
在这里插入图片描述
咱们代码创建的线程t 默认是前台线程,在执行过程中,进程是不能结束的,main 执行完 start 直接结束了.main 不影响了.只要前台线程没执行完,进程就不会结束.即使 main 已经执行完毕了
设为 true 是后台(后台,是躲在背后的人,你感知不到)后台不会阻止进程结束
不设为 true 是前台(前台,是明面上的人,你能感知到)前台会阻止进程结束
在这里插入图片描述
再次执行,发现控制台啥都没打印,就没了,进程就结束了!!
在这里插入图片描述
注意! !
此处也有一定的概率, 出现t打印一次,然后结束进程的情况。这个事情就看是 main 先执行结束,还是t先执行一次打印(线程之间是抢占式执行,调度顺序不确定)
但是按照经验来看,当前代码结构中,大概率是啥都不打印的。即使你尝试 1w 次,结果可能都是t啥都不打印。
概率不均等的原因在于main 调用 start 速度很快。对t来说,系统要把t线程创建出来之后才能执行打印,创建 本身有时间开销,虽然比进程创建轻量,但是也不是为0.
为什么有的人执行了很多次 都是一定打印?
多线程程序中,存在很多神奇的操作,很多代码,稍微变动一点或者代码即使不变,换了个机器,换了个运行环境,结果都不一样.(难点所在)在这里插入图片描述

isAlive():表示了内核中的线程(PCB)是否还存在。Thread 实例和内核的线程的生命周期,并非是一致的(可能存在, Thread 对象还存活,但是系统中的线程已经销毁的情况)

isAlive()
表示了内核中的线程(PCB)是否还存在。Java 代码中创建的 Thread 对象和系统中的线程是一一对应的关系,代码中定义的线程对象 (Thread) 实例虽然表示一个线程,但这个对象本身的生命周期和 内核中的 pcb 生命周期,是不完全一样的~
在这里插入图片描述
t.start(),才真正在内核中创建出这个 pcb, 此时 isAlive 就是 true
当线程 run 执行完了,此时 内核中的线程就结束了(内核 pcb 就释放了)。但是此时 t变量可能还存在,于是 isAlive 也是 false
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3 启动⼀个线程 - start()

之前我们已经看到了如何通过覆写 run ⽅法创建⼀个线程对象,但线程对象被创建出来并不意味着线程就开始运⾏了。
• 覆写 run ⽅法是提供给线程要做的事情的指令清单
• 线程对象可以认为是把 李四、王五叫过来了
• ⽽调⽤ start() ⽅法,就是喊⼀声:”⾏动起来!“,线程才真正独⽴去执⾏了。在这里插入图片描述
调⽤ start ⽅法, 才真的在操作系统的底层创建出⼀个线程

  1. 调用 start 动作本身是非常快的~
    一旦执行 start, 代码就会立即往下执行,不会产生任何的阻塞等待
    在这里插入图片描述
  2. 一个线程对象只能 start 一次~
    Thread 类使用 start 方法, 启动一个线程。对于同一个 Thread 对象来说, start只能调用一次!
    在这里插入图片描述
    在这里插入图片描述
    非法线程的状态异常:start 里面对线程状态做了判定。线程执行了 start 之后, 就是就绪状态/阻塞状态了。对于 就绪状态/阻塞下 线程不能再次 start
    要想启动更多线程,就是得创建新的对象!!!
    调用 start 创建出新的线程,本质上是 start 会调用系统的 api, 来完成创建线程的操作.

经典面试题:start 和 run 区别 (这个问题,大家一定要注意理解)

(start 和 run 其实是八竿子打不着,互不相干的内容, 但是就是有人搞的不太对)
run 是线程的入口方法, 不需要手动调用;start 是调用系统 api
在这里插入图片描述
在这里插入图片描述

2.4 中断⼀个线程

中断,也是操作系统中的一个"专用术语"。更好的说法,终止一个线程(终止线程, 在 Java 中,都只是"提醒,建议",真正要不要终止,还得线程本体来进行决定的 !! t 线程,正在执行其他线程,只能提醒一下t是不是要终止了,t 收到这样的提醒之后,也还是得自己决定的)线程之间调度是随机的,万一人家线程正在做一个很重要的工作,干了一半,强制让人家结束,可能就会引起一些 bug.

李四⼀旦进到⼯作状态,他就会按照⾏动指南上的步骤去进⾏⼯作,不完成是不会结束的。但有时我们需要增加⼀些机制,例如⽼板突然来电话了,说转账的对⽅是个骗⼦,需要赶紧停⽌转账,那张三该如何通知李四停⽌呢?这就涉及到我们的停⽌线程的⽅式了。
让一个线程能够结束,核心就是让线程的入口方法(run 方法)执行完毕,线程就随之结束了(run 方法尽快 return) 一一>非常取决于 具体代码 实现方式了

⽬前常⻅的有以下两种⽅式:

  1. 通过共享的标记来进⾏沟通
    为了让线程结束,引入标志位.在这里插入图片描述
    通过上述代码,就可以让线程结束掉,具体线程啥时候结束,取决于在另一个线程中何时修改 isQuit 的值
    main 线程,想要让 t 线程结束,大前提一定是t线程的代码,对这样的逻辑有所支持,而不是t里的代码随便咋写都能提前结束的。如果代码没有配合,main 无法让 t 提前结束的!!在这里插入图片描述
    run 方法和 main 方法是两个线程,这俩线程的执行顺序是不确定的!!!(时刻牢记)
    我们注意到开头把isQuit设成了成员变量,如果把 isQuit 作为 main 方法中的 局部变量, 是否可行!!!?在这里插入图片描述
    不可行!运行发现编译报错了,为啥会报错???在这里插入图片描述
    lambda 表达式 讲过的一个语法,变量捕获
    lambda 表达式/匿名内部类,是可以访问到外面定义的局部变量的!!!(变量捕获语法规则)。变量捕获,本质上就是把外面的变量当做参数传进来了(参数是隐藏的)
    你这个捕获的变量,得是 final 或者" 事实 final “(虽然没写 final 但是没有修改,虽无夫妻之名,但行夫妻之实)
    在这里插入图片描述
    由于此处 isQuit 确实要修改!!!不能写成 final 也不是"事实 final,局部变量这一手,就行不通!!! 因此就必须写作成员变量。
    为啥写作成员变量就可以了??又是哪个语法规定的???
    lambda 表达式,本质上是"函数式接口” ——> 匿名内部类,内部类访问外部类的成员,这个事情本身就是可以的!!! 这个事情就不受到变量捕获的影响了。
    为啥 java 这里对于变量捕获有 final 的限制?
    isQuit 是局部变量的时候是属于 main 方法的栈帧中;但是 Thread lambda 是有自己独立的栈帧的 (另一个线程中的方法)。这两个栈帧的生命周期不一致的!这就可能会导致,main 方法执行完了,栈帧销毁了,同时 Thread 的栈帧还在,还想继续使用 isQuit 。
    Java 中的做法就非常的简单粗暴:变量捕获本质上就是传参,换句话说,就是让 lambda 表达式在自己的栈帧中创建一个 新的 isQuit,并把外面的 isQuit 值给拷贝过来(为了避免 例外 isQuit 的值不同步,java 干脆就不让你 isQuit修改)
    java 语法里变量捕获已经挺简单了
    相比之下 C++ 里, 变量捕获更复杂了。就需要程序猿手动控制:按照值方式捕获,还是按照引用方式捕获(手动确保生命周期正确),还是按照右值引用的方式捕获。虽然不受到 final 的影响, 可以随意修改,但编码复杂度大幅度提升
    相比之下 JS 里, 变量捕获也很复杂,JS 改了变量的生命周期。某个局部变量被其他"匿名函数"捕获生命周期了,此时这个变量就脱离原有的函数级别的(这背后就涉及到一个非常复杂的"作域链"问题/闭包.……)
    不要求一下都能理解,慢慢品~
  2. 调⽤ interrupt() ⽅法来通知
    通过刚才的写法,不够优雅, Thread 类还提供了一种更优雅的选择:让 Thread 对象, 内置了这个变量。这个代码本质上,就是使用 Thread 实例内部自带的标志位,来代替刚才手动创建的 isQuit 变量了
    Thread.currentThread() 这个操作,是获取当前线程实例(t),哪个线程调用,得到的就是哪个线程的实例(类似于 this)
    在这里插入图片描述
    执行代码,可以看到代码中出现了一个异常,t 线程并没有真的结束!!!
    在这里插入图片描述
    刚才这里的 interrupt 导致sleep 出现异常!!!
    如果没有 sleep, interrupt 可以让线程顺利结束,有 sleep 引起了变数!! 在执行 sleep 的过程中,调用 interrupt大概率 sleep 休眠时间还没到, 被提前唤醒了。提前唤醒,会做两件事:
    1.抛出 InterruptedException(紧接着就会被 catch 获取到)
    2.清除 Thread 对象的 isInterrupted 标志位
    只有sleep会清除异常吗? 一一>不只是 sleep ,很多方法都会
    通过 interrupt 方法, 已经把标志位设为 true。但是 sleep 提前唤醒操作,就把标志位又设回 false(此时循环还是会继续执行了)
    要想让线程结束,只需要在 catch 中加上 break 就行了~在这里插入图片描述
    sleep 清空标志位,是为了给程序猿更多的“可操作性空间”
    前一个代码,写的是 sleep(1000),结果现在 1000 还没到, 就要终止线程。这就相当于是两个前后矛盾的操作。
    此时,是希望写更多的代码,来对这样的情况进行具体的处理的。此时程序猿就可以在 catch 语句中,加入一些代码,来做一些处理
    (1)让线程立即结束: 加上 break
    (2)让线程不结束,继续执行: 不加 break
    (3)让线程执行一些逻辑之后,再结束: 写一些其他代码,再 break
    比如我在游戏,我妈让我去买酱油:(1)立即停下游戏,立即去买(2)无视我妈,装作没听见,继续打游戏(3)给我妈说,我打完这把,再去买
    有的人可能会抛出另一种异常
    旧版本的 idea 生成 try catch,catch 里头自动给的代码是 打印调用栈。新版本的 idea生成的代码,是再抛出另一个异常.
    实际开发中,catch 语句中的代码,既不会是打印调用栈,也不会是 throw 另一个异常,idea 生成的这两种代码, 都只是占个位置而已. 没啥实际的作用!!!
    实际开发中,catch 里应该要写什么样的代码???
    (如果你的程序出现异常了,该如何处理,是更合理的??? )对于一个服务器程序来说,稳定性是非常重要的!!! 无法保证服务器就一直不出问题,这些所谓"问题"在 java 代码中, 就会以 异常 的形式体现出来,可以通过 catch 语句,对这些异常进行处理
    (1)尝试自动恢复
    能自动恢复, 就尽量自动回复。比如出现了一个 网络通信 相关的异常,就可以在 catch 尝试重连网络
    (2)记录日志(异常信息记录到 文件中)
    有些情况, 并非是很严重的问题,只需要把这个问题记录下来即可.(并不需要立即解决)后面程序猿有空的时候再解决
    (3)发出报警
    针对一些比较严重的问题了! 包括不限于,给程序猿 发邮件,发短信,发微信, 打电话.….
    (4)也有少数的正常的业务逻辑,会依赖到 catch
    比如文件操作中有的方法,就是要通过 catch 来结束循环之类的…[非常规用法]
    当前阶段,catch 就随意了~ catch 代码放到整个项目代码的哪个层次,都是非常讲究的。《代码大全》也有章节讨论这样的话题~

⽰例-1: 使⽤⾃定义的变量来作为标志位.
需要给标志位上加 volatile 关键字(这个关键字的功能后⾯介绍).

public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 啊!险些误了⼤事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");target.isQuit = true;}
}

⽰例-2: 使⽤ Thread.interrupted() 或者
Thread.currentThread().isInterrupted() 代替⾃定义标志位.

Thread 内部包含了⼀个 boolean 类型的变量作为线程是否被中断的标记
在这里插入图片描述

使⽤ thread 对象的 interrupted() ⽅法通知线程结束.

public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种⽅法均可以while (!Thread.interrupted()) {//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName() + ": 有内⻤,终⽌交易!");// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName() + ": 啊!险些误了⼤事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName() + ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName() + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");thread.interrupt();}
}

thread 收到通知的⽅式有两种:

  1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
    ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程.
  2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过
    ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到

在 Java 中,线程的终止,是一种"软性"操作。必须要对应的线程配合,才能把终止落实下去。
相比之下, 系统原生的 api,其实还提供了强制终止线程的操作。无论你线程是否愿意配合,无论线程执行到哪个代码,都能强行把这个线程给干掉!! 这样的操作,java 的 api 中没有提供的。
上述强制执行的做法,弊大于利的。如果强行干掉一个线程,很可能线程执行到一半,就可能会出现一些残留的临时性质的"错误"的数据。
假设这个线程正在执行 写文件 操作. 写文件的数据有一定的格式要求(写一个图片文件),如果写图片写了一半,线程嘎了,图片就尴尬了 图片文件,是存在,里面的内容不正确 ,无法正确打开了。

2.5 等待⼀个线程 - join()

有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。
多个线程的执行顺序是不确定(随机调度,抢占式执行)。虽然线程底层的调度是无序的,但是可以在应用程序中,通过一些 api, 来影响到线程执行的顺序。join 就是一种方式影响的线程结束的先后顺序~ 比如, t2 线程等待 t1 线程,此时,一定是 t1 先结束,t2 后结束,join 是可能会使 t2 线程阻塞。
线程 run 方法中的内容执行时间不可预期,使用 join 就可以很好的解决问题。
在这里插入图片描述
main 线程中,调用 t.join()。让 main 线程 等待 t 线程结束 [ 谁等谁,这个事情,一定要搞清楚! ](系统原生的 api 就是这样设定)
执行 join 的时候, 就看t线程是否正在运行。如果 t运行中,main 线程就会阻塞(main 线程就暂时不去参与 cpu 执行了;如果 t运行结束,main 线程就会从阻塞中恢复过来, 并且继续往下执行(阻塞, 使这俩线程的结束时间,产生了先后关系)
join方法用的多吗?线程最核心的 api之一,用的非常多!!!
一个典型情况:使用多个线程并发进行一系列的计算,用一个线程阻塞等待上述计算线程,等到所有的线程都计算完了,最终这个线程汇总结果~
那直接按顺序写一个线程不就可以了吗?
线程的执行顺序不确定,线程执行的任务的时间也是不可预期的。如果单个线程,无法发挥多核 cpu 的优势(算的慢);如果多个线程,势必是需要有一个线程进行汇总结果的。注意join不是确定的"执行顺序”,而是确定的"结束顺序"
任何一个线程都可以调用 join, 规则和之前说的是一样的。哪个线程调用 join 哪个线程就阻塞等待。

例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要⼀个⽅法明确等待线程的结束。

public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName()  + ": 我还在⼯作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始⼯作");thread1.start();thread1.join();System.out.println("李四⼯作结束了,让王五开始⼯作");thread2.start();thread2.join();System.out.println("王五⼯作结束了");}
}

⼤家可以试试如果把两个 join 注释掉,现象会是怎么样的呢?
在这里插入图片描述

2.6 获取当前线程引⽤

这个⽅法我们已经⾮常熟悉了
Thread.currentThread() 获取到当前线程的 引用(Thread 的引用)
如果是继承 Thread,直接使用 this 拿到线程实例;如果是 Runnable 或者 lambda 的方式,this 就无能为力了。此时 this 已经不再指向 Thread 对象了!! 就只能使用 Thread.currentThread();在这里插入图片描述

public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

2.7 休眠当前线程

也是我们⽐较熟悉⼀组⽅法,有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证
实际休眠时间是⼤于等于参数设置的休眠时间的。
在这里插入图片描述

public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}
http://www.dtcms.com/a/291176.html

相关文章:

  • Netty中CompositeByteBuf 的addComponents方法解析
  • PNP加速关断驱动电路
  • [数据结构]#4 用链表实现的栈结构
  • FastAPI 中,数据库模型(通常使用 SQLAlchemy 定义)和接口模型(使用 Pydantic 定义的 schemas)的差异
  • GraphRAG快速入门和原理理解
  • 在线教育如何设置视频问答/视频弹题?——重塑在线教育的互动体验
  • 元宇宙工厂漫游指南:VR可视化在设备巡检与远程运维中的沉浸式应用
  • Component cannot be used as a JSX component
  • 网络数据编码技术及其应用场景的全面解析
  • 基于Vue与CloudBase AI Toolkit的色觉识别Web应用开发报告:VibeCoding新范式实践
  • 基于dcmtk的dicom工具 第八章 echoSCU-dicom测试连接
  • 广东餐饮服务中级水平测试精选题库
  • 基于Python的多传感器融合的障碍物检测与避障演示
  • WPF 项目设置应用程序图标和设置程序集图标
  • 搭建种草商城框架指南
  • 修复WSL安装失败(错误: 0x80248014 )并安装K8S
  • 低空经济展 | 约克科技携小型化测试设备亮相2025深圳eVTOL展
  • Linux物理地址空间入门:从硬件到内核内存的基石
  • Javascript--事件
  • 耐达讯RS232转Ethercat网关:建筑驱动连接的“秘密武器”
  • 【系统全面】Socket编程——基础知识介绍
  • 2x2矩阵教程
  • AI赋能中医传承:智慧医疗新时代解决方案
  • 如何避免redis分布式锁失效
  • 搭建前端页面,介绍对应标签
  • 前端之学习后端java小白(一)之SDKMAN
  • Typecho目录树插件开发:从后端解析到前端渲染全流程
  • AI革命带来的便利
  • [特殊字符] Java反射从入门到飞升:手撕类结构,动态解析一切![特殊字符]
  • 多线程--线程池