【多线程】线程的五种创建方法

文章目录

  • 线程
  • 在 Java 代码中编写多线程程序
    • Thread 标准库
  • 创建线程的写法
    • 1 . 继承 Thread 类
      • 代码
      • 回调函数
      • 休眠操作:`sleep()`
      • 抢占式执行
      • 观察线程
        • jconsole
        • IDEA 内置调试器
    • 2 . 实现 Runnable 接口
      • 代码
    • 3. 匿名内部类创建 Thread ⼦类对象
      • 代码
      • 匿名内部类
    • 4.匿名内部类创建Runnable⼦类对象
      • 代码
    • 5.lambda 表达式创建 Runnable ⼦类对象

线程

  • 可以理解成更轻量的进程,也能解决[[01 计算机是如何工作的#^87b85a|并发编程]]的问题,但是创建/销毁的开销,要比进程更低

    • 因此多线程编程就成了当下主流的并发编程方式

    • 这个基本上是我们以后工作中天天用到,面试中考查的重点
      #高频面试

    • 在系统中,线程同样是通过 [[01 计算机是如何工作的#^7eb7b0|PCB]] 来描述的(Linux)

      • 一个进程,是一组 PCB
      • 一个线程,是一个 PCB
    • 一个进程中可以包含多个线程

      • 此时每个线程都可以独立的去 CPU 上调度执行

        • 线程是系统“调度执行”的基本单位
        • 进程是系统“资源分配”的基本单位
      • 一个可执行程序运行的时候(双击)

        • 操作系统会创建进程,给这个程序分配各种系统资源(CPU,内存,硬盘,网络带宽…)
        • 同时也会在这个进程中创建一个或多个线程,这些线程再去 CPU 上调度执行
      • 同一个进程中的线程,共用同一份资源

    • 线程比进程更轻量主要体现在可以资源共用

      1. 创建线程,省去了“分配资源”的过程
      2. 销毁进程,省去了“释放资源”的过程
      • 一旦创建进程,同时也会创建第一个线程==>分配资源,时间相对来说较慢

        • 一旦后续创建第二个、三个线程,就不必再重新分配资源了,用创建第一个线程时分配的资源
      • 当线程数目越来越多之后,此时效率也没办法进一步提升了(桌子的空间是有限的),当滑稽老铁数目达到一定程度后,有些人就够不到桌子了,就吃不到了
        62be5ee9b54355070b636fdcd9e0d3f.jpg|227

        • 能够提升效率,关键是充分利用多核心进行“并行执行

        • 如果只是“微观并发”,速度是没有提升的,真正能提升速度的是“并行

        • 如果线程数目太多,比如超出了 CPU 核心数目,此时就无法在微观上完成所有线程的“并行执行”,势必会存在严重的竞争

        • 当线程多了之后,此时就容易发生“冲突”

        • 由于多个线程,使用的是同一份资源(内存资源

        • 若多个线程针对同一个变量进行读写操作(尤其是写操作),就容易发生冲突

        • 一旦发生冲突,就可能使程序出现问题==>“线程安全问题

        • 一旦某个线程抛出异常,这个时候,如果不能妥善处理,就可能导致整个进程都崩溃,因此其他线程就会随之崩溃



  • 进程和线程的概念与区别(高频面试题,操作系统话题下出场频率最高的问题
    #高频面试

    1. 进程包含线程
      • 一个进程里面可以有多个线程
    2. 进程是系统资源分配的基本单位
      线程是系统调度执行的基本单位
    3. 同一个进程的线程之间,共用一份系统资源(内存,硬盘,网络带宽等)
      • 尤其是“内存资源”,就是代码中定义的变量/对象…
      • 编程中,多个线程,是可以共用一份变量的
    4. 线程是当下实现并发编程的主流方式,通过多线程,就可以充分利用好多核 CPU
      • 但也不是线程数越多就一定越好,当线程数达到一定的程度,把多个核心都利用充分之后,再增加线程,就无法再提高效率了,甚至可能会影响效率(线程调度也是有开销的)
    5. 多个线程之间可能会相互影响
      • 线程安全问题:一个线程抛出异常,也可能会把其他线程也一起带走
    6. 多个进程之间一般不会相互影响
      • 进程的隔离型:一个进程崩溃了,不会影响其他进程

在 Java 代码中编写多线程程序

  • 线程本身是操作系统提供的概念,操作系统提供 API 供程序猿调用
    • 但不同的系统,提供的 API 是不同的(Windows 创建线程的 API 和 Linux 的差别非常大)
    • Java(JVM)把这些系统 API 封装好了,咱们不需要关注系统原生 API,只需要了解好 Java 提供的这一套 API 就好了

Thread 标准库

  • 这个类负责完成多线程的相关开发

创建线程的写法

1 . 继承 Thread 类

代码

package thread;  class MyThread extends Thread {  //继承Thread类的目的是重写里面的run()方法  @Override  public void run() {  //这里写的代码就是即将创建的线程所需要执行的逻辑  while (true) {  System.out.println("hello thread");  //休眠操作,避免CPU消耗过大  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  throw new RuntimeException(e);  }  }}
}  public class Demo1 {  public static void main(String[] args) {  MyThread t = new MyThread();  //创建线程,与主线程各自独立,并发执行  t.start();  //t.run(); 不会创建新线程,在主线程中执行,但执行不到//因为由于不是线程,所以不会并发执行,所以一直执行创建的MyThread线程,死循环while(true) {  System.out.println("hello main");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  throw new RuntimeException(e);  }  }}  
}

这个代码运行起来是一个进程,但包含了两个线程

主线程——>main 方法(每个进程必有)
自创建的新线程——>t.start()

随后主线程和新线程就会并发/并行的在 CPU 上执行

  1. 创建一个类,继承于标准库的 Thread,并重写 run() 方法

    • Thread 类可以直接被继承,因为它来自 java. lang 这个包,而这个包是默认导入的,里面所有的类都可以直接使用
    • 继承 Thread 类的主要目的是重写 run() 方法
    • run() 中的方法就是即将创建出的线程要执行的逻辑
    1. main 方法里创建刚才那个类的实例,再使用提供的 start() 方法来创建线程

      • 调用 run() 就会在进程内部创建出一个新的线程,新的线程就会执行刚才 run 里面的代码
      • 线程明细:
        1. 主线程:调用 main 函数的方法需要一个专门的线程来执行,称为主线程
        2. t1.start();:这是创建的一个新进程,与主线程之间是并发/并行关系在 CPU 上执行
          • 这里调用 start() 是创建了一个新的线程,随后执行新线程里面的逻辑,而直接调用 run() 方法的话不会创建新的线程
    2. 运行结果 image.png|107


回调函数

非常重要的概念

  • run() 方法并没有被手动调用,但是最终也执行了,
  • 这种被用户定义了,但没手动调用的方法,最终是被系统/库/框架调用了,此时这样的方法就叫“回调函数(callback)
    • Java 数据结构中,优先级队列(堆),必须先定义好对象的“比较规则”
    • Comparable==>compareTo
      comparator==>compare
      都是自己定义了,但没有调用,此时都是由标准库本身内部的逻辑负责调用的

休眠操作:sleep()

  • 可以让循环每循环一次就休息一下,避免 CPU 消耗过大。单位是 ms(毫秒),1000 ms = 1 s
  • 这是一个静态方法类方法
    • 可以直接通过类名进行访问 类名. 方法名,不需要实例化对象,通过对象来访问

    • 这里会报错,使用 Alt+Enter

      • IDEA 自动生成 try/catch,catch 中默认的代码有两种风格

        1. 再次抛出一个异常:throw new RuntimeException(e);
        2. 只是打印异常信息:e.printStackTrace();
        • 但实际开发中不止这俩
          • 可能进行“重试
          • 可能进行“回滚
          • 可能会通过短信/邮件/微信/电话向程序猿报警

抢占式执行

  • 多个线程之间,谁先去 CPU 上调度执行,这个过程是“不确定的”,这个调度顺序取决于内核里面的“调度器”的实现
    • 调度器里面有一套规则,但是我们作为程序开发没法进行干预,也感受不到,只能把这个过程近似于随机

观察线程

jconsole
  • 可以借助第三方工具来看这两个进程的情况
    • JDK 中的 bin 目录(binary 二进制,里面放的都是可执行程序)
      • 通过这个可以看到 Java 中进程运行情况
        image.png|277
      • 远程进程:其他机器上的进程,需要通过网络连接
      • 本地进程:正在运行的进程
        • 一个 Java 线程中,不仅仅只有一个线程,其实有很多
        • 代码中自己创建的线程命名的规律是 Thread-数字
          image.png|202
      • 主要的线程(主线程和自己创建的线程)
        • 调用栈(非常有用)
          当代码出现问题,抛出异常,进程终止时,可以查看对应的调用栈找到出现问题的语句,以及这个代码是如何一层一层被调用过去的 image.png|398
      • 其他进程:主要起到辅助作用 image.png|156
        1. 垃圾回收:在合适的时机,释放不使用的对象
        2. 统计信息/调试信息:比如现在通过 jconsole 能查看到一个 Java 进程的详情
IDEA 内置调试器
  • 通过 IDEA 内置的调试器,也能看到类似的信息 image.png|313

2 . 实现 Runnable 接口

代码

package thread;  //通过Runnable的方式来创建线程  
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 {  MyRunnable runnable = new MyRunnable();  Thread thread = new Thread(runnable);  //通过Thread创建线程,线程要执行的任务是通过Runnable来描述的,不是通过Thread自己thread.start();  while (true) {  System.out.println("hello main");  Thread.sleep(1000);  }}
}

因为 Runnable 是一个 interface(接口),所以要用 implements(实现)

Runnable 是用来描述“要执行的任务”是什么
通过 Thread 创建线程,线程要执行的任务是通过 Runnable 来描述的,不是通过 Thread 自己来描述的

两种本质上差别不大,第二种更利于“解耦和
这个 Runnable 只是一个任务,并不与“线程”这样的概念强相关
后续执行这个任务的载体可以是线程,也可以是其他的东西

[!quote] 协程纤程

  • 线程是轻量级进程,但进程很重
  • 随着对性能要求的提高,开始嫌弃线程,引入协程
  • 也叫做虚拟线程

3. 匿名内部类创建 Thread ⼦类对象

本质是继承 Thread,和 1 一样

代码

package thread;  public class Demo3 {  public static void main(String[] args) throws InterruptedException {  Thread thread = new Thread() {  //这里就是在定义匿名内部类,这个类是Thread的子类  public void run() {  //在类内部重写run方法  while (true) {  System.out.println("hello thread");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  throw new RuntimeException(e);  }                }           }        };        thread.start();  while (true) {  System.out.println("hello main");  Thread.sleep(1000);  }    }
}

匿名内部类

[!quote] 匿名内部类

  • 一般是一次性的,用完就丢了
  • 内聚性更好一些
    • 相关联的代码放的越集中,内聚性越好
Thread thread = new Thread() {  //这里就是在定义匿名内部类,这个类是Thread的子类  public void run() {  //在类内部重写run方法   }
};
  • 这一段代码的解释
    1. 定义匿名内部类,这个类是 Thread 的子类
    2. 类的内部,重写了父类的 run 方法
    3. 创建了一个子类的实例,并且把实例的引用复制给了 thread

4.匿名内部类创建Runnable⼦类对象

本质是通过匿名内部类实现

代码

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

5.lambda 表达式创建 Runnable ⼦类对象

  • 本质上就是匿名内部类,是一个更简化的写法
  • 很多时候,写“匿名内部类”,目的不是写“类”,而是为了写那个 run() 方法
  • lambda 可以直接表示我们要写的 run() 方法,省去了一些不需要的部分
public static void main(String[] args) {  Thread thread = new Thread(() -> {  while(true){  System.out.println("hello thread");  try{  Thread.sleep(1000);  }catch(InterruptedException e){  throw new RuntimeException(e);  }        }    });    thread.start();  while(true){  System.out.println("hello main");  try{  Thread.sleep(1000);  }catch(InterruptedException e){  throw new RuntimeException(e);  }  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/390712.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCB设计经验——布线原则

1.连线精简——避免直角布线 导线也应看作一种元器件,有自己的电阻,电感,电容 PCB走线在直角转弯的地方,信号前后部分相互影响,导致分布电容增加,对信号上升沿和下降沿有延缓影响。从阻抗的角度来说&#…

海康笔试题

1. 2. 块设备:磁盘设备驱动、SD设备驱动 字符设备:终端设备驱动 网络设备:网络设备驱动 (1)linux操作系统驱动程序分为三大类:字符设备驱动、快设备驱动和网络设备驱动 (2)字符设…

渗透测试--DHCP饿死实验

实验拓扑 实验步骤 Router Router(config)#int f0/0 Router(config-if)#ip address 192.168.100.254 255.255.255.0 Router(config-if)#no shutdown Router(config-if)#exitRouter(config)#ip dhcp pool NAME Router(dhcp-config)#network 192.168.100.0 255.255.255.0 Route…

过期知识:thinkphp5 使用migrate给现有的数据表新增表字段

个人开发网站记录, 这个文章主要是个以后健忘的我看的. 我在搞我的画笔审核 , 发现数据表的画笔数据在审核驳回的时候还是软删除好一些, 免得用户找不到之前上传的画笔数据, 后期也可以考虑重新显示给用户,让用户可以修改画笔信息重新提交审核. 这个时候想起了…

脚拉脚模型笔记

脚拉脚模型 ⌈♪⌋例题: 辅助线(中点)做法: 倍长中线Rt △ △ △ 斜边中线等腰 △ △ △ 三线合一中位线 需要:两个等腰三角形,顶角互补 共__底点__ 底角需要连接 解: ∵ D Q 1 / 2 A B O…

【Qt】QDial和QSlider

QDial QDial类用于创建一个旋转式的圆形控件,通过鼠标点击旋转、方向键或者pageUp和pageDown调整一个值。常用在需要进行连续调整的场景,比如音量控制、亮度控制、透明度调节等 常见属性 属性说明value持有的值minimum持有值所能到达的最小值maximum持有…

【C语言】C语言期末突击/考研--函数

目录 一、函数的声明与定义-嵌套调用 1.1.函数的声明与定义 1.2.函数的分类与调用 二、函数的递归调用 三、局部变量与全局变量 3.1.全局变量解析形参实参解析 3.2.局部变量与全局变量 四、练习题及解析 一、函数的声明与定义-嵌套调用 1.1.函数的声明与定义 函数间的…

操作系统原理:程序、进程、线程的概念

文章目录 程序、进程、线程的概念程序(Program)进程(Process)线程(Thread)关系总结 在日常对操作系统的使用中,大家肯定对程序、进程和线程多少有所耳闻。作为操作系统的重要一部分,…

R 语言学习教程,从入门到精通,R的安装与环境的配置(3)

1、R 基础语法 一门新的语言学习一般是从输出 “Hello, World!” 程序开始&#xff0c;R 语言的 “Hello, World!” 程序代码如下&#xff1a; myString <- "Hello, World!" print ( myString )以上示例将字符串 “Hello, World!” 赋值给 myString 变量&#x…

# mongodb_基础到进阶 -- MongoDB 高级--MongoDB 集群部署与安全性(四)

mongodb_基础到进阶 – MongoDB 高级–MongoDB 集群部署与安全性&#xff08;四&#xff09; 一、mongodb 第一个路由节点创建 1、分片集群架构目标 两个分片节点副本集&#xff08;33&#xff09;一个配置节点副本集&#xff08;3&#xff09;两个路由节点&#xff08;2&am…

day17 Java流程控制——用户交互Scanner

day17 Java流程控制——用户交互Scanner 目录 day17 Java流程控制——用户交互Scanner1. 什么是Scanner对象&#xff1f;2. 实操 1. 什么是Scanner对象&#xff1f; Scanner对象是Java编程语言中的一个类&#xff0c;存在于java.util包中。它用于获取输入&#xff0c;可以是各…

【letcode-c++】242有效的字母异位词与49字母异位词分组

一、242 有效的字母异位词 &#xff08;1&#xff09;题目 &#xff08;2&#xff09;知识点–哈希 【这一段总结来自于代码随想录的讲解学透哈希表 哈希的优势是可以实现快速查找&#xff0c;它非常适合应用与查找某一个元素是否在一个集合中出现。 哈希有三种实现形式&…

C++篇:入门(2)

引用 引用的概念以及定义&#xff1a; 在C中&#xff0c;引用&#xff08;Reference&#xff09;是一个非常重要的概念又可以称之为取别名&#xff0c;它允许我们创建一个已存在对象的别名。引用提供了一种机制&#xff0c;通过它可以直接访问另一个变量、对象或函数的值&#…

【Python 逆向滑块】(实战五)逆向滑块,并实现用Python+Node.js 生成滑块、识别滑块、验证滑块、发送短信

逆向日期&#xff1a;2024.08.03 使用工具&#xff1a;Python&#xff0c;Node.js 本章知识&#xff1a;滑块距离识别&#xff0c;滑块轨迹生成&#xff0c;验证滑块并获取【validate】参数 文章难度&#xff1a;中等&#xff08;没耐心的请离开&#xff09; 文章全程已做去敏处…

【时时三省】(C语言基础)函数递归练习

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 求字符串长度 求的是arr里面字符串的长度 abc后面还有一个\0为结束标志 在结算字符串长度的时候不算\0 所以它的长度是3 模拟实现一个strlen函数 str等于\0的时候就会结束返回count 如果…

一款简单且强大的免费开源图片压缩软件

图压是一款简单易用且功能强大的图片压缩工具&#xff0c;适用于Windows和macOS两大操作系统。它能够在几乎不损害图片清晰度的情况下&#xff0c;显著减小图片的体积&#xff0c;特别适合需要在网页、PPT、Word、PDF中使用的图片压缩。图压的操作界面简洁&#xff0c;用户可以…

2024智慧农场土地租赁家禽认养众筹实时监控商品溯源农业积分商城秒杀助农小程序源码

后端&#xff1a;系统后端使用PHP语言开发 前端&#xff1a;前端使用uniapp进行前后端分离开发 功能简介&#xff1a;土地种植、农业认养、积分商城、农场活动、视频监控、农场商城、实时数据监控、限时秒杀、农业众筹、送货上门、一键分销、农场入驻、全部店铺 运行环境&am…

PyMongo

什么是PyMongo PyMongo 是一个 Python 库&#xff0c;用于与 MongoDB 数据库进行交互。MongoDB 是一个基于文档的 NoSQL 数据库&#xff0c;提供高性能、可扩展性和灵活的架构。PyMongo 提供了一套工具&#xff0c;使得在 Python 程序中操作 MongoDB 变得简单和高效。 安装PyMo…

SpringBoot配置文件高级用法实战

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

u盘数据丢失怎么办?以下这四招教你轻松找回!

重要的文件资料要是不小心手滑删除了&#xff0c;轻则遭受领导的责骂&#xff0c;重则就是直接受到老板的警告了&#xff0c;所以打工人应该要人手必备数据找回的技巧&#xff0c;尤其是在针对已经拷贝好了数据在u盘当中时&#xff0c;更是需要快速去找到数据恢复的技巧&#x…