当前位置: 首页 > 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方法执行完,程序(进程)就结束了,实际上,以前的认知只是针对单线程程序的

相关文章:

  • 模型自学推理:自信驱动进化
  • Linux程序与进程
  • Android LiveData 详解
  • 查询oracle进程数和会话数进行优化
  • 友达光电12.1寸液晶屏G121XN01 V001工控屏
  • 深入浅出DeepSeek:从零开始的AI编程指南
  • There is a chart instance already initialized on the dom. 柱状图初始化时报前面这个错误如何解决?
  • 龙虎榜——20250528
  • LeeCode 94. 二叉树的中序遍历
  • 74道Node.js高频题整理(附答案背诵版)
  • 简乐 1.4.0 | 非常简洁 无损下载 畅听全网
  • 头歌之动手学人工智能-Pytorch 之autograd
  • 王树森推荐系统公开课 排序05:排序模型的特征
  • 【NLP】将 LangChain 与模型上下文协议 (MCP) 结合使用
  • 华为OD机试真题——模拟工作队列(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • bat 批处理通过拖拽,来获取拖入文件的信息
  • Linux之高效文本编辑利器 —— vim
  • 【动态规划】子数组系列(二)
  • CSP 2024 提高级第一轮(CSP-S 2024)阅读程序第一题解析
  • Typora中文直装版
  • 网站开发前景咋样/网站怎么收录
  • 网站推广的渠道/新手seo入门教程
  • 路由侠怎么做网站映射/seo第三方点击软件
  • 网站群系统/360地图怎么添加商户
  • 企业网站优化是什么/广告网站留电话不用验证码
  • 网站建设培训 ppt/怎么找到精准客户资源