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

【多线程初阶】初识线程 创建线程

文章目录

  • 🌅认识线程
    • 🌊 什么是线程
    • 🌊为什么要使用线程
    • 🌊进程和线程的区别
    • 🌊Java线程和操作系统线程的关系
  • 🌅第一个多线程程序
    • 🌊使用jconsole命令观察线程
    • 🌊创建线程
      • 🏄‍♂️方法一:继承Thread类 重写run
      • 🏄‍♂️方法二:实现Runnable接口 重写run
      • 🏄‍♂️方法三:本质是方法一使用匿名内部类
      • 🏄‍♂️方法四:使用Runnable 匿名内部类
      • 🏄‍♂️方式五:针对三,四改进,引入lambda表达式(推荐使用)

🌅认识线程

🌊 什么是线程

一个线程就是一个"执行流"每个线程之间都可以按照顺序执行自己的代码,多个线程之间"同时"执行着多份代码

举个例子:一家公司去银行办理业务既要财务转账,又要福利发放,还要缴纳社保,朝新一个会计肯定忙不过来,耗费时间也会非常长,为了更快办理业务,朝新又找来两位同时,小舟和朝望,凡个人分别负责一个事情,自从就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务

此时我们就把这种情况称为多线程,将一个大任务分解成不同个小任务,交给不同执行流就分别排队执行,其中小舟和朝望都是朝新叫来的,所以朝新一般为称为主线程(Main Thread)

🌊为什么要使用线程

首先,“并发编程"是"刚需”

  • 单核CPU的发展遇到瓶颈,就需要多核CPU,而并发编程能更充分利用多核CPU资源
  • 有些任务场景需要"等待IO",为了让等待IO的时间能去做一些其他工作,也需要用到并发编程

其次,虽然多进程也能实现并发编程,但是线程比进程更轻量

  • 创建线程比创建进程更快
  • 销毁线程比销毁进程更快
  • 调度线程比调度进程更快

最后,线程虽然比进程轻量,但是后面又有了"线程池"(ThreadPool) 和 协程 (Coroutine)

🌊进程和线程的区别

  • 1.进程包含线程,每个进行至少有一个线程的存在,即主线程
  • 2.进程和线程之间不共享内存空间,同一个进程的线程之间才共享同一个内存空间

比如之前的多线程例子,每个客户来办理业务,他们之间的票据肯定不想让别人看到,而当时朝新,小舟和朝望虽然是不同的执行流,但都是为一家公司办理,所以票据共享,这就是多线程和多进程最大的区别

  • 3.进程是系统分配资源的最小单位,线程是系统调度的最小单位
  • 4.一个进程挂了一般不会影响到其他进程,但是一个线程挂了,可能把同进程内的其他线程一起带走(整个进程崩溃)

🌊Java线程和操作系统线程的关系

线程是操作系统中的概念,操作系统内核实现了线程这样的机制,并且对用户提供了一些API使用

Java标准库中的Thread类可以视为对操作系统提供的API进行了进一步的抽象和封装

🌅第一个多线程程序

  • 每个线程都是一个独立的执行流
  • 多个线程之间是并发执行的

在这里插入图片描述

class MyThread extends Thread{@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
public class Demo1 {public static void main(String[] args) throws InterruptedException {//1.创建 Thread 子类,在子类中重写 run 方法Thread t = new MyThread();t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

注意:
1.Thread.sleep(); 无法进行throws,只能try catch 是因为他的父类Thread写的时候是没有throws的,子类重写的父类,必须与父类保持一致
2.main方法里的Thread.sleep()虽然是向上抛出异常,只不过是换了个形式,在实际开发中一般不会这样搞
3.有时候main在前,有时候thread在前
4.是因为多个线程.调度顺序是随机的,这俩线程,谁先执行,谁后执行,都有可能,无法预测(而且编写代码,也不能依赖这两个逻辑的执行顺序)
5.这是抢占式执行,由操作系统内核的调度器控制,我们没办法在应用程序中编写代码控制(调度器没有提供API)
6.唯一能做的是给线程设置优先级(但是优先级,对于操作系统来说,也是仅供参考,不会严格的定量的遵守)

在这里插入图片描述

🌊使用jconsole命令观察线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🌊创建线程

🏄‍♂️方法一:继承Thread类 重写run

class MyThread extends Thread{@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
public class Demo1 {public static void main(String[] args) throws InterruptedException {//1.创建 Thread 子类,在子类中重写 run 方法Thread t = new MyThread();t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

🏄‍♂️方法二:实现Runnable接口 重写run

class MyRinnable implements Runnable{@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Demo2 {public static void main(String[] args) throws InterruptedException {Runnable runnable = new MyRinnable();Thread t = new Thread(runnable);//传递参数t.start();while (true){System.out.println("hello main");Thread.sleep(1000);}}
}

Runnable runnable = new Runnable();
就是一个任务,一段要执行的逻辑
最终还是要通过Thread,真正的创建线程
线程要干啥—>通过Runnable来表示(而不是通过重写 Thread run来表示了)
线程要执行任务的定义,是放到Thread里面,还是放到外面(Runnable中)

这样写也是让要执行的任务本身 和 线程这个概念能够解耦合从而后续如果变更代码(比如不通过线程执行这个任务,通过其他方式…),采用Runnable这样的方案,代码修改就会更简单

  • 高内聚低耦合

平时应该有听说过,我们写代码尽量追求高内聚低耦合,那么什么是高内聚,什么是低耦合
高内聚:比如我们的衣服,如果随便乱放,我们要找一件衣服时,他可能会出现在,衣柜里,沙发上,床上,洗衣机里,晾衣架上,椅子上等等等等甚至要遍历家里的每一个角落,这就是低内聚;我们希望快点找到衣服就需要把衣服聚集起来,比如我们就把衣服放在两个地方,衣柜里和晾衣架上,那么我们找衣服时只需要找两个地方,这就是高内聚,效率明显增高
低耦合:比如你和你的小学同学,微信上仅仅是朋友圈的点赞之交,同学如果要结婚了,对你压根没啥影响,你该干啥干啥,朋友圈点个赞就可以了,这就是低耦合;要是你的亲哥哥姐姐结婚了,你肯定要参加的,你会为此推掉一些邀约啥的,这边出现情况会对你影响很大,这就是高耦合

代码中我们希望低耦合,写代码基本原则,代码的可维护性更好(好改),开发中尽量考虑到让代码之间解耦合,即使修改,只修改其中一小部分即可

对应到代码里
写了一个项目,有很多的代码,很多的文件,也有很多类,很多逻辑把有关联的各种代码,放到一起,只要和某个功能逻辑相关的定西,都放在这一块,就是高内聚
如果某个功能的代码,东一块,西一块就是低内聚

高内聚(一个模块内,有关联的东西放一起),低耦合(模块之间,依赖尽量小,影响尽量小)

🏄‍♂️方法三:本质是方法一使用匿名内部类

在这里插入图片描述

public class Demo3 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(){@Overridepublic 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.创建了一个Thread的子类 --子类叫啥?不知道,匿名的!
2.{ }里面就可以编写子类的定义代码,子类里要有哪些属性,哪些方法,重写父类哪些方法
3.创建了这个匿名内部类的实例,并且把实例的引用赋值给 t

在这里插入图片描述
使用匿名内部类可以少定义一些类,一般如果某个代码是"一次性的"就可以使用匿名内部类的写法

🏄‍♂️方法四:使用Runnable 匿名内部类

public class Demo4 {public static void main(String[] args) throws InterruptedException {Runnable runnable = new Runnable() {@Overridepublic void run() {while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};Thread t = new Thread(runnable);t.start();while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

直接继承 Thread,执行任务本身 和 Thread(线程)这个概念是耦合在一起的,如果有一天需要把这里的任务,通过其他方式执行(不使用多线程,而是线程池或者协程),就需要把代码进行大规模的调整,使用Runnable,任务和线程概念是分离的

🏄‍♂️方式五:针对三,四改进,引入lambda表达式(推荐使用)

  • 本质上就是一个匿名函数,主要用途就是作为"回调函数"
  • ( ) -> { } 创建了一个匿名的函数式接口的子类,并且创建出对应的实例并且重写里面的方法(编译器在背后做的事)
public class Demo5 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {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);}}
}
  • 观察demo6感受多线程程序
  • 已经查不到main线程了,说明main方法执行完成,但是程序没有结束
public class Demo6 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (true) {System.out.println("hello 1");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();Thread t2 = new Thread(() -> {while (true) {System.out.println("hello 2");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t2.start();Thread t3 = new Thread(() -> {while (true) {System.out.println("hello 3");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t3.start();for (int i = 0; i < 3; i++) {System.out.println("hello main");Thread.sleep(1000);}}
}

在这里插入图片描述

main主线程,已经结束了,main方法执行完毕,主线程就结束了,但程序没有结束
以前的认知,main方法执行完,程序(进程)就结束了,实际上,以前的认知只是针对单线程程序的

http://www.xdnf.cn/news/692137.html

相关文章:

  • 线性回归中标准方程法求逆失败的解法:正则化
  • 三维点云深度学习代码torch-points3d-SiamKPConvVariants复现记录(持续更新中)
  • MAC程序签名遇到的问题
  • 用结构填充平面
  • GUI 编程——python
  • PortSwigger-02-XXE
  • Gerapy二次开发:在Ubuntu服务器中利用pyenv+supervisor实现项目部署
  • 为 MCP Server 提供 Auth 认证,及 Django 实现示例
  • 三、zookeeper 常用shell命令
  • Spring AI 代理模式(Agent Agentic Patterns)
  • 基于Vue3.0的【Vis.js】库基本使用教程(002):图片知识图谱的基本构建和设置
  • 机器学习-随机森林
  • 算法训练第一天
  • 深度解析 torch.mean 的替代方案
  • Web前端快速入门(Vue、Element、Nginx)
  • 通过海康萤石API控制家里相机的云台及抓图
  • PHP:从Web开发基石到现代应用引擎的进化之路
  • 青岛市长任刚与深兰科技董事长陈海波会谈,深兰青岛项目即将进入快车道!
  • Nacos注册中心原理
  • System Properties 和 Settings.Global 的区别
  • 尚硅谷redis7 70-72 redis哨兵监控之案例实操7
  • go实现定时任务
  • QT 5.15.2 程序中文乱码
  • Linux基础 -- Linux 启动调试之深入理解 `initcall_debug` 与 `ignore_loglevel`
  • JavaScript核心总结与现代化应用指南
  • 弥散制氧机工作机制:高原低氧环境的氧浓度重构技术
  • Laravel单元测试使用示例
  • linux安装ffmpeg7.0.2全过程
  • es6 函数解构
  • 【系统架构设计师】2025年上半年真题论文回忆版: 论事件驱动架构及应用(包括解题思路和参考素材)