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

初识多线程

1. 认识线程(Thread)

1) 线程是什么

⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码.
银行的例子:
⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。
如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两 位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情,分别申请⼀个号码进⾏排队,⾃此就有 了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务
此时,我们就把这种情况称为多线程, 将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队 执⾏ 。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(Main Thread)。

2) 为啥要有线程

⾸先, "并发编程" 成为 "刚需".
单核 CPU 的发展遇到了瓶颈. 要想提⾼算⼒, 就需要多核 CPU. ⽽并发编程能更充分利⽤多核 CPU 资源.
有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做⼀些其他的⼯作, 也需要⽤到并发编程.
其次, 虽然多进程也能实现 并发编程, 但是线程⽐进程更轻量.
创建线程⽐创建进程更快.
销毁线程⽐销毁进程更快.
调度线程⽐调度进程更快.
最后, 线程虽然⽐进程轻量, 但是⼈们还不满⾜, 于是⼜有了 "线程池"(ThreadPool) 和 "协程"
(Coroutine)

3) 进程和线程的区别

进程是包含线程的. 每个进程⾄少有⼀个线程存在,即主线程。
进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间.
⽐如之前的多进程例⼦中,每个客⼾来银⾏办理各⾃的业务,但他们之间的票据肯定是不想让别⼈知 道的,否则钱不就被其他⼈取⾛了么。⽽上⾯我们的公司业务中,张三、李四、王五虽然是不同的执 ⾏流,但因为办理的都是⼀家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最⼤区 别。
进程是 系统分配资源 的最⼩单位,线程是 系统调度 的最⼩单位。
⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带⾛(整个进程崩溃).

4) Java 的线程 和 操作系统线程 的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对⽤⼾层提供了⼀些 API 供⽤⼾ ,使⽤(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进⾏了进⼀步的抽象和封装.
1.2 第⼀个多线程程序
感受多线程程序和普通程序的区别:
  1.  每个线程都是⼀个独⽴的执⾏流
  2. 多个线程之间是 "并发" 执⾏的.

2.实现多线程

1.通过继承Thread父类来实现多线程

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 {MyThread t = new MyThread();// 启动线程t.start();// 主线程循环打印while(true){System.out.println("hello main");Thread.sleep(1000);}}
}

关键点说明:

  • Thread.start() vs Thread.run()

    • start() 方法启动新线程并调用 run() 方法。而直接调用 run() 方法只会在当前线程中执行该方法的内容,并不会创建新线程。
    • 如果你调用 t.run(),那么实际上还是在主线程中执行 run() 方法的代码,而不是在新线程中执行。
  • 线程的并发执行

    • 主线程和子线程是并发执行的,打印的顺序是由操作系统的线程调度器决定的,可能会在不同的环境下表现出不同的输出顺序。

也可以通过匿名内部类来实现:

public class Demo3 {public static void main(String[] args) throws InterruptedException {// 创建线程的匿名内部类并实现 run() 方法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);  // 每秒打印一次}}
}
匿名内部类
Thread t = new Thread(){@Overridepublic void run(){while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
  • 你通过创建一个 Thread 类的匿名内部类来覆盖 run() 方法。
  • 在 run() 方法中,线程执行的代码逻辑是一个无限循环,打印 "hello thread" 每秒一次。
  • 这种方式相较于继承 Thread 类的方式,避免了显式创建一个子类,可以让代码更简洁。

2.通过实现Runnable接口来实现多线程:

class MyRunnable 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 {MyRunnable runnable = new MyRunnable();Thread t = new Thread(runnable);  // 创建线程并传入任务t.start();  // 启动线程while (true) {System.out.println("hello main");Thread.sleep(1000);  // 主线程每秒打印一次 "hello main"}}
}

为什么可以这样传参??

Thread t = new Thread(runnable);  // 创建线程并传入任务

 Runnable 接口

public interface Runnable {
    void run();  // 任务的执行代码
}

 

在 Java 中,Runnable 是一个函数式接口,它有一个 run() 方法。这个接口是用来表示某个可以由线程执行的任务的。具体来说,它代表了一个任务的“行为”,即一段可以并行执行的代码。一个实现了 Runnable 接口的类就可以定义如何执行该任务。

2. Thread 构造函数接受 Runnable 对象

Thread 类有多个构造函数,其中一个构造函数是:

public Thread(Runnable target)

使用匿名内部类:

public class Demo4 {public static void main(String[] args) throws InterruptedException {// 通过匿名内部类来实现 Runnable 接口Thread t = new Thread(new Runnable() {@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);  // 每秒输出一次 "hello main"}}
}

3.通过Lambda表达式来实现多线程:

public class Demo5 {public static void main(String[] args) throws InterruptedException {// 使用 Lambda 表达式创建线程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);  // 主线程每秒打印一次}}
}

hread类的常见属性和构造方法:

  • Thread类常见的构造方法:

  • Thread类常见的属性:
public class Demo6 {public static void main(String[] args) {Thread t = new Thread(()->{while(true){System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}},"自定义线程");//->线程名字为自定义线程t.start();System.out.println("线程ID : " + t.getId());System.out.println("线程名字: " + t.getName());System.out.println("线程状态: " + t.getState());System.out.println("线程执行顺序:" + t.getPriority());}
}

普通线程(前台线程):

public static void main1(String[] args) {Thread t1 = new Thread(() -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();
}
  • Thread t1 = new Thread(() -> { ... }):通过 Lambda 表达式创建了一个线程,并定义了线程执行的任务。任务就是不断打印 "hello thread",然后每隔 1 秒 (Thread.sleep(1000)) 暂停。
  • t1.start():启动线程。

在这种情况下,线程 t1 是一个普通线程,也就是 用户线程。程序结束时,所有的用户线程都必须结束,才能让 JVM 退出。

2. main 方法(后台线程)

public static void main(String[] args) {Thread t2 = new Thread(() -> {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});//把 t 设置线程为后台线程(守护线程),不能阻止程序结束t2.setDaemon(true);t2.start();
}
  • t2.setDaemon(true):这一行代码将线程 t2 设置为守护线程。守护线程是一个特殊的线程,它的作用是支持其他线程的工作。守护线程在没有其他用户线程(非守护线程)运行时会自动退出。换句话说,只要程序中的所有用户线程都结束了,JVM 就会退出,即使守护线程还在运行

    • 普通线程(用户线程)会阻止 JVM 退出,直到它们执行完毕。
    • 守护线程在所有用户线程结束后会立即退出。
  • t2.start():启动线程。


到这里竹竹零就要和大家说再见了

9a90bc9fb4c3409c9569951569288f5a.png

希望时光不负赶路人,愿我们做最好的自己!!!

如果您觉得有失偏颇请您在评论区指正,如果您觉得不错的话留个好评再走吧!!

您的鼓励就是对我最大的支持!  ! !


http://www.mrgr.cn/news/67254.html

相关文章:

  • JAVA读取doc,docx转PDF通过vue展示
  • WebGUI之Gradio:Gradio 5的简介、安装和使用方法、案例应用之详细攻略
  • 后端java——如何为你的网页设置一个验证码
  • 【vim文本编辑器gcc编译器gdb调试器】
  • 享元模式及其运用场景:结合工厂模式和单例模式优化内存使用
  • .NET使用TDengine时序数据库和SqlSugar操作TDengine
  • Linux 系统目录结构
  • 分布式中常见的问题及其解决办法
  • Go + Wasm
  • C#-类:静态成员的介绍
  • C++进阶-->红黑树的实现
  • ECCV2024新鲜出炉!动态再训练-更新用于无源目标检测的Mean Teacher
  • 真题--数组循环题目
  • 【找规律】
  • Prometheus启动参数配置及释义
  • 计算机视觉读书系列(1)——基本知识与深度学习基础
  • webworker
  • HJ48 从单向链表中删除指定值的节点
  • **AI的三大支柱:神经网络、大数据与GPU计算的崛起之路**
  • RHCE作业四
  • 实验7-3-4 字符串替换
  • 2024年11月7日 十二生肖 今日运势
  • 【前端】MQTT:通信与聊天室实战
  • 三十三、Python基础语法(面向对象其他语法-下)
  • 非关系型数据库NoSQL的类型与优缺点对比
  • 基于 Vue3、Vite 和 TypeScript 实现开发环境下解决跨域问题,实现前后端数据传递