目录1.认识线程Tread1.1概念1线程是什么2为什么要有线程3进程和线程的区别非常高频的操作系统面试题没有之一①进程包含线程②虽然多进程也能实现并发编程但是线程比进程更轻量③进程和进程间不共享内存空间但同一个进程的线程之间共享同一个内存空间④进程是系统分配资源的最小单位线程是系统调度的最小单位⑤一个进程挂了一般不影响到其他进程但是一个线程挂了可能带着进程内的其他线程一起挂4Java的线程和操作系统线程的关系API答疑1.2第一个多线程程序实际开发中异常的处理方式答疑啥时服务器对比普通程序查看线程情况1.3创建线程方法1继承Thread类重写run方法1.继承Thread来创建一个线程类2.创建MyThread类的实例3.调用start方法起点线程方法2实现Runnable接口1.实现Runnable接口重写run方法2.创建Thread类实例调用Thread的构造方法时将Runnable对象作为target参数3.调用start方法其他变形方法3匿名内部类创建Thread子类对象方法4匿名内部类创建Runnable子类对象方法5lambda表达式创建Runnable子类对象1.认识线程Tread1.1概念1线程是什么每个进程都相当于一个要执行的任务每个线程也是一个要执行的任务运行的一段代码指令多个线程之间“同时”执行着多份代码2为什么要有线程单核CPU遇到瓶颈就需要多核心CPU而并发编程能更充分利用多核CPU的资源进程是多任务操作系统的一个重要环节通过多进程编程的方式可以实现“并发编程”效果拿服务器开发来解释一下并发编程MySQL客户端服务结构的程序MySQL服务器保存数据的本体一个服务器程序同一时刻要给多个客户端提供服务服务器就像餐馆一样客户端则是来吃饭的客人而多进程的方式就是一种解决方案每一个客户端请求发到服务器上服务器提供一个进程给这个客户端进行服务进程整体是一个比较“重”的概念创建进程/销毁进程 开销比较大尤其是频繁的创建销毁为了解决上述问题引入线程Thread是一种轻量级进程创建销毁的开销更小3进程和线程的区别非常高频的操作系统面试题没有之一下面请出滑稽哥辅助理解~~滑稽哥需要吃完100只①进程包含线程每个进程至少存在一个线程即主线程一个进程里很有可能只有一个线程比如我们从C一直学到现在写的程序都是单线程的程序Windows的任务管理器看不到进程内部的线程需要借助一些其他调试工具VS的调试器、Windbg……②虽然多进程也能实现并发编程但是线程比进程更轻量创建线程/销毁线程/调度线程都会进程快1.多进程实现并发编程搞两个房间、两套桌子、两个滑稽老铁~每个滑稽负责吃50只即可整体效率就能够大幅提升~2.多线程实现并发编程在一个房间进程搞一个桌子、两个滑稽线程来吃~此时也是每个滑稽吃50只同时吃整体的效率仍然能大幅提高还节省了一间房子和桌子的开销③进程和进程间不共享内存空间但同一个进程的线程之间共享同一个内存空间进程和进程之间所涉及的资源是各自独立的彼此互不干扰存在稳定性~进程内部管辖的多个线程之间会共享内存资源、外存资源、网络宽带……创建和销毁是一个“重量级”的事情进程创建需要申请资源进程销毁需要释放资源但对于线程来说只是第一个线程创建的时候和进程一起创建的时候申请资源后续再创建线程不涉及到资源申请操作干的事儿少、快只有所有的线程都销毁进程销毁才真正释放资源运行过程中销毁某个线程也不会释放资源我们发现滑稽都在一个房间意味着线程共享同一个内存空间~3.如果引入更多线程呢每个滑稽吃25只整体效率还能进一步提高~4.滑稽再多点时发现即使提高线程数目能够提升效率也不是“线性增长”线程数目达到一定程度后就算线程再多也没法起到效果了因为桌子上的空间就那么大~而且挤来挤去还会拖慢效率~线程数目如果太多线程的调度开销也会非常明显因为调度开销拖慢程序的性能5.只要这些滑稽是共享同一个桌子此时就有可能抢同一只~如果两个滑稽看中了同一块肉此时就可能产生冲突~这就是线程安全问题也叫线程不安全这样的冲突可能会使代码出现Bug~④进程是系统分配资源的最小单位线程是系统调度的最小单位CPU资源很难使用“共享”词来描述~CPU好比舞台每个线程就是演员每个线程都有上舞台表演的机会线程在CPU上执行的这一系列过程和上节课讲的“进程调度”其实也是线程调度其实是一样的线程是CPU上调度执行的基本单位如果一个进程包含多个线程此时多个线程之间各自去CPU上调度执行比如进程里有线程1、2、3很有可能是并行的⬇️线程1去CPU核心1上执行线程2去CPU核心2上执行线程3去CPU核心3上执行也有可能是并发⬇️线程1、线程2、线程3在一个核心上来回切换也有可能⬇️线程1、线程2在一个核心上来回切换线程3在另一个核心上和别的进程的线程来回切换……具体这些线程是怎么调度执行的这个过程咱们程序员感知不到也干预不了全是由操作系统内部“调度器”自行完成的与进程类似每个线程除了调度都有一份这样的数据状态优先级记账信息上下文一个进程有10个线程就有10份这样的数据~~但是这10个线程共用一个文件描述符表和内存指针~⑤一个进程挂了一般不影响到其他进程但是一个线程挂了可能带着进程内的其他线程一起挂6.当某个滑稽老铁没有抢到之后非常生气~直接掀桌(╯‵□′)╯︵┻━┻难办~就别办~都别吃了~一个线程抛出异常可能会带走整个进程所有线程都无法继续工作~、如果及时捕获到处理掉也不一定导致进程终止~4Java的线程和操作系统线程的关系线程是操作系统中的概念操作系统内核实现了线程这样的机制并且对用户层提供了一些API供用户使用例如Linux的pyhread库1.操作系统提供的原生线程API是C语言的2.不同操作系统的线程API不一样Java标准库中Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装闲聊啥是APIAPIApplication Programming Interface应用程序编程接口说白了就是别人写了一些类/函数你拿过来直接就能用其实现在API是一个广义的概念操作系统提供的标准库第三方库其他的各种开源项目工作中隔壁项目组给你提供的一份代码……以上都是API出现的场景API是别人给你提供的程序/库都能干啥~~比如两个小情侣谈恋爱刚认识的时候女孩给男孩提供的API是1.发QQ2.发短信后来更熟悉了女孩给男孩提供的API是1.打电话2.发视频后来确定关系了女孩给男孩提供的API是1.亲亲抱抱举高高2.吃饭3.看电影API答疑Q1MySQL中的视图也是API吗不是API的目的是用于去“编程”Q2写SQL语句不是吗SQL可以认为是MySQL服务器给用户提供的API不是函数形式的、也不是类形式的、而是语言级别的Q3Java原生提供的包是API吗是的比如标准库提供的APIString、StringBufferList、ArrayListRandomSystem.out……1.2第一个多线程程序闲聊Demo术语啥意思我们经常接触一些其他人提供的库/框架提供案例Demo/Sample/quick starttest呢一般是测试用例更详细的测试的过程package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-15 * Time: 11:42 */ class MyThread extends Thread{ //run相当于线程的入口 Override //Thread父类中本身有一个run方法这个方法就是线程要执行的任务 //程序员编写自己的逻辑替代自身的run public void run() { System.out.println(hello thread); } } public class Demo1 { public static void main(String[] args) { //为啥不import包也能使用Thread类 //java.lang包是默认导入的 Thread tnew MyThread();//向上转型将子类对象转为父类对象 //真正再系统中创建出一个线程 t.start(); } }但是我们发现在t.start()下面写一个t.run()也有相同的效果体现不出多线程程序和普通程序的区别但本质上是start创建了一个新线程多了一个执行流能够干活~这个代码就可以“一心两用”同时做两件事下面来感受一下两个线程package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-15 * Time: 11:42 */ class MyThread extends Thread{ //run相当于线程的入口 Override //Thread父类中本身有一个run方法这个方法就是线程要执行的任务 //程序员编写自己的逻辑替代自身的run public void run() { for (int i0;i100;i) { System.out.println(hello thread); } } } public class Demo1 { public static void main(String[] args) { //为啥不import包也能使用Thread类 //java.lang包是默认导入的 Thread tnew MyThread();//向上转型将子类对象转为父类对象 //真正再系统中创建出一个线程 t.start(); // t.run(); for (int i0;i100;i) { System.out.println(hello main); } } }其中第一个线程public static void main(String[] args) { Thread tnew MyThread(); //真正再系统中创建出一个线程 t.start(); // t.run(); for (int i0;i100;i) { System.out.println(hello main); } }第二个线程class MyThread extends Thread{ //run相当于线程的入口 Override public void run() { for (int i0;i100;i) { System.out.println(hello thread); } } }结果发现它们在交替打印在“同时”执行闲聊我们写这个代码的时候如果换成whiel(true)会发现风扇狂转~这俩while循环跑的太快消耗了太多CPUCPU就会以更高的频率功率工作产生更多的热量台式机就还好轻薄本散热差更明显一些~电脑太热真的会炸吗降频从而以更低的功率工作性能也会降低笔记本的性能普遍比台式机要差的~市面上1w块的笔记本性能大概相当于5k-6k的台式机~我们没必要让CPU跑那么快因此我们可以采用 Thread.sleep(1000);来休眠让当前的线程暂时放弃CPU休息1000ms注意由于可以直接类名调用因此sleep是静态方法编程中的时间单位1s1000ms(毫秒)、1ms1000us(微秒)但是我们发现这个正是我们Java SE时见到的异常受查异常编译时异常非受查异常运行时异常而这个属于受查异常解决方式1.往上抛回去throw2.直接捕获住try-catchAltEnter后发现发现这里的解决方案只有一个try-catch而不能进行throw为啥呢因为throw得在run方法上加但是很遗憾父类Thread的run中没有throws那么我们在子类重写的时候就不能加因为子类父类不但要方法名一致还要方法的参数、抛出哪些异常也都得一致因为有这个限制我们就不能throw但是在main方法上就可以throws因为main方法并不是重写的方法是允许你往外抛异常的综上所述run方法中只能用try-catch处理异常然后我们再来观察IDEA给我们的代码try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); }我们发现它是捕获了这个异常然后又往上抛了个新异常只是换了个形式捕获的是个受查异常然后再往上抛的是个非受查异常所以就没有报错了但这只是IDEA的默认解决方式而已在实际开发中我们一般不会这么搞~因为这并没有解决任何问题~实际开发中异常的处理方式1.记录异常信息作为日志后续程序员根据日志调查问题适用于程序仍然正常往后执行逻辑不会因为这个异常就终止对服务器非常关键~因为服务器是7×24小时运行的要同时给多个客户端提供服务如果处理一个请求的过程中出现异常导致程序崩溃此时就无法给其他客户端提供服务了2.进行重试有的异常重试一下有概率就好了多见于网络通信3.特别严重的问题必须马上处理可以在异常处理中通过短信/邮件/微信/电话通知程序员报警机制答疑啥是服务器咱们以后大概率就是去做服务器开发~~指的是两个程序软件服务器客户端这两个程序配合完成一些工作客户端client主动发起请求的一方服务器server被动接受请求返回响应的一方1.请求和响应请求Request客户端给服务器发的数据响应Response服务器给客户端返回的数据2.通常一个服务器可以给多个客户端提供服务有的场景下服务器可能只给一个专属客户端提供服务分布式的场景3.服务器不知道客户端啥时候来因此需要早早就把程序跑起来一直持续运行7×24小时也有例外如果你的这个服务器特别diao也可以不用7×24小时~比如12306买票每天晚上23:00~7:00停服维护能查询但是不能买~闲聊最累的公司和曾经最轻松的公司~1.华为-最累的公司互联网公司加班程度top1的水平~①关于作息早上8点上班~晚上一般凌晨0-1点下班2点3点也不是没有甚至说当时22点之前想下班需要跟领导请假~午休是12:30-14:00遇到紧急公关的时候需要24小时待命~一周通常工作六天周六妥妥的得去周日不一定任务不紧张可以不去任务紧张可以去半天左右~②关于加班费绝大部分公司是没有加班费的~加班费为0华为这个公司从来没有加班费和腾讯字节不一样等你工作满一年让你签“奋斗者协议”主动放弃加班费自愿成为奋斗者~~不签就等于自愿放弃升职加薪下次裁员大概率裁你~③关于薪资待遇相对于其他一般公司来说华为的钱给的不少但是和阿里腾讯字节美团这些一线公司比华为的薪资没有优势~同一水平但分红的话跟各个公司一样高级别的都有④那为啥还那么多人去华为华为校招策略非常简单粗暴学校一刀切985学校同学校招大概率都能有华为保底实在没有更好的选择了华为也不是不能去~2.搜狗-曾经最轻松的公司①关于作息互联网行业中加班最少的~约等于010:30上班19:30下班午休12:00-14:00而且18:00还是晚饭时间一周工作五天双休有保障每年还有15天带薪年假所有互联网公司的最高规格②关于薪资待遇给的钱一点也不比其他一线公司的少包括华为③还有各种奇怪的假~3.8妇女节女生放假一天5.4青年节28岁以下的放假一天……还有过年探亲假、生日、入职周年、男生节、程序员节……甚至还有“狗胜节”也放假~~狗胜节就是2015年阿尔法狗大战李世石AI下围棋王小川笃定AI完胜~第一个工作日搜狗设为狗胜节全员放假一天~④还有各种福利各种发礼品有个礼品网站大家可以自选而且每个礼品都不便宜大多都两三百的可惜的是搜狗公司已经无了~~搜狗现在已经被腾讯收购了腾讯的一个部门现在是啥样也不清楚~~接下来我们就发现电脑不会那么热了~package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-15 * Time: 11:42 */ class MyThread extends Thread{ //run相当于线程的入口 Override public void run() { while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo1 { public static void main(String[] args) throws InterruptedException { Thread tnew MyThread(); //真正再系统中创建出一个线程 t.start(); // t.run(); while(true){ System.out.println(hello main); Thread.sleep(1000); } } }我们再来观察一下这个打印信息我们发现有时候是main在前有时候是thread在前这是因为多线程是“抢占式执行”多个线程的调度顺序是随机的这俩线程谁先执行谁后执行都有可能无法预测因为这是操作系统内核的调度器控制的咱们没法在应用程序中编写代码控制调度器没有API我们唯一能做的就是给线程设置优先级但是优先级对于操作系统来说也是仅供参考不会严格的定量遵守这是第一节第三弹中讲过的“记账信息”知识会给一直冷落的舔一些甜头让他继续舔~因此我们编写代码的时候也不能依赖这俩逻辑的执行顺序对比普通程序此时我们注释掉上述代码中的t.start()改成t.run()我们会发现打印信息变成了此时整个进程中只有一个main线程因为 t.run() 这个操作没有创建线程只是调用了刚才重写的run而已main方法对应的线程就是一个进程至少要包含的那个线程即主线程查看线程情况可以借助第三方工具查看线程的详细情况只是针对Java进程来说其实这都是JDK的一部分~我们先找到我们Java的JDK装在哪里如果有的小伙伴忘记了可以在IDEA上找到找项目结构这样就能找到具体路径了~然后我们在路径中找到 jcnsole.exe文件如果双击之后看不到进程列表就右键管理员方式运行~注意如果没有你要找的那个进程可能是因为这个程序已经终止了~出现这个直接点不安全连接即可然后就会出现下面的图像各个部分反映的信息另一方面我们通过Java自带的调试器也能看到一些线程相关的信息多线程编程是服务器开发这边最常见的编程模型工作中经常接触多线程多线程工作中常用面试中常考闲聊画图板、QQ音乐、cctalk这些程序主要是两种1.C2.C#微软自己的语言Windows自带的很多程序都是C#PC上Java写的程序不多也就IDEA、MC……Java主战场服务器开发、安卓开发安卓APP大部分都是Java写的基于前端技术体系也能写APP、C也能写安卓其实在安卓上写APP还有一个Kotlin谷歌官方主推的语言Kotlin是JetBrainsIDEA公司搞的这个语言编译出来的是和Java兼容的字节码也是运行在JVM上的~也就是说可以白嫖Java的生态Java的所有的库Kotlin都可以无缝直接拿来使用但是Kotlin写的库Java也能调用~还有一些其他的比如Scala、Groove……都是类似的白嫖做法……1.3创建线程方法1继承Thread类重写run方法1.继承Thread来创建一个线程类class MyThread extend Thread{ Override public void run(){ System.out.println(hello Thread); } }2.创建MyThread类的实例MyThread tnew MyThread();3.调用start方法起点线程t.start();//线程开始运行方法2实现Runnable接口1.实现Runnable接口重写run方法class MyRunnable implements Runnable{ Override public void run(){ System.out.println(hello Thread); } }2.创建Thread类实例调用Thread的构造方法时将Runnable对象作为target参数Thread tnew Thread(new MyRunnable());3.调用start方法t.start();//线程开始运行package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-15 * Time: 22:34 */ class MyRunnable implements Runnable { Override public void run() { while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } public class Demo2 { public static void main(String[] args) throws InterruptedException { Runnable runnablenew MyRunnable(); Thread tnew Thread(runnable); t.start(); while(true){ System.out.println(hello main); Thread.sleep(1000); } } }我们发现效果与方法一是一模一样的~对比上面两种方法方法1继承Thread类相当于把线程要执行的任务的定义放到Thread里面方法2实现Runnable接口相当于把线程要执行的任务的定义放到Thread外面Runnable中Runnable runnablenew MyRunnable();就是一个可执行的任务最终还是要通过Thread真正创建线程线程里要干啥通过Runnable来表示而不是直接通过重写Thread run来表示了这个过程的目的主要就是为了“解耦合”说白了就是程序猿写代码时遵循的“高内聚低耦合”让要执行的任务本身和线程这个概念能够解耦合从而后续如果变更代码比如不通过线程执行这个任务通过其他方式……采用Runnable这样的方案代码的修改就会更简单高内聚一个模块之内有关联的东西放在一起低耦合模块之间依赖尽量小影响尽量小阶段性小结1.线程是什么线程是轻量级进程解决的问题进程太重了创建销毁进程开销比较大2.线程和进程的区别进程包含线程进程是操作系统资源分配的基本单位线程是操作系统调度执行的基本单位同一个进程的多个线程之间共用一份资源内存、文件3.关于线程调度操作系统内核进行控制的程序员在应用程序这个层次无法感知、也无法干预4.Java对于线程使用Thread区别start和runThread tnew MyThread(); t.start();start是真正在系统中创建线程JVM调用操作系统的API完成线程创建操作class MyThread extends Thread{ Override public void run() { System.out.println(hello thread); } }run是线程的入口方法新的线程启动了就要执行这里的代码run不需要咱们手动调用新的线程创建好了之后自动的去执行~因此run方法相当于“回调函数”这个Runnable runnable表示一个“可以执行的任务”基于这种写法能更好的 解耦合~什么是回调函数学到现在至少见到两次了~1.C语言进阶部分指针进阶部分函数指针部分函数指针的两个应用场景①回调函数②转移表数组每个元素是一个函数指针能够降低代码的时间复杂度2.Java数据结构优先级队列的指定比较规则部分Comparable compareToComparator compare其中compareTo和compare也属于“回调函数”你写一个函数你自己不去调用交给别人来调用~其他变形方法3匿名内部类创建Thread子类对象//使用匿名类创建Thread子类对象 Thread tnew Thread(){ Override public void run(){ System.out.println(使用匿名类创建Thread子类对象); } };Thread tnew Thread(){ };这个代码做了三件事①创建了一个Thread的子类子类叫啥名字不知道匿名②{ }里就可以编写子类的定义代码子类里要有哪些属性、要有哪些方法、重写父类的哪些方法……③创建了这个匿名内部类的实例并且把实例的引用赋值给tpackage thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-16 * Time: 15:27 */ public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread tnew Thread(){ Override public void run() { while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t.start(); while(true){ System.out.println(hello main); Thread.sleep(1000); } } }我们发现以上代码的效果跟方法1、方法2的效果是相同的都是并发执行对比方法1我们发现只是把线程的代码换了个位置在Java中如果某个代码是“一次性”的那么就可以使用匿名内部类的写法这样就可以少定义一些类了~方法4匿名内部类创建Runnable子类对象//使用匿名类创建Runnbale子类对象 Thread tnew Thread(new Runnable(){ Override public void run(){ System.out.println(使用匿名类创建Runnable子类对象); } });与上述内容相同代码如下效果也是一样的就不再赘述了~package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-16 * Time: 16:36 */ public class Demo4 { public static void main(String[] args) throws InterruptedException { Runnable runnablenew Runnable() { Override public void run() { while (true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; Thread tnew Thread(runnable); t.start(); while (true){ System.out.println(hello main); Thread.sleep(1000); } } }方法5lambda表达式创建Runnable子类对象方法5相当于基于方法3和方法4的进一步改进~闲聊lambdalambda表达式本质上就是一个“匿名函数”最主要的用途就是作为“回调函数”很多语言C、Python、JS……都有只不过叫法可能有所差异~Java中有个很尴尬的事情就是方法必须要依托于“类”存在但实际上我们可能只需要一个函数就行了还要搞个类就很麻烦~所以Java后来弄了一个“函数式接口”意思是这个接口中就有这么一个方法然后就简化为了lambda表达式[]-{}虽然我们写法上简单了一些但本质上还是没有脱离“类”因为实际上是创建了一个匿名的函数式接口的子类并且创建出对应的实例并且重写了里面的方法编译器在背后做的事情~//使用lambda表达式创建Runnbale子类对象 Thread t3new Thread(()-System.out.println(使用匿名类创建Thread子类对象)); Thread t4new Thread(()-{ System.out.println(使用匿名类创建Thread子类对象); });整体代码如下我们会发现打印效果跟之前是一模一样的~package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-16 * Time: 16:58 */ public class Demo5 { public static void main(String[] args) throws InterruptedException { //()相当于匿名函数的参数列表 Thread tnew Thread(()-{ while(true){ System.out.println(hello thread); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); while(true){ System.out.println(hello main); Thread.sleep(1000); } } }我们会发现lambda表达式只保留了方法中最核心的方法体部分其余需要重复固定写的部分全省略了这也是我们推荐的写法~