【多线程初阶】Thread类常见方法以及线程的状态

news2024/11/23 4:34:37

多线程初阶系列目录

持续更新中

1.第一次认识线程
2.Thread类常见方法以及线程的状态


文章目录

  • 多线程初阶系列目录
  • 前言
  • 1. Thread 类及常见方法
    • 1.1 常见构造方法
    • 1.2 常见属性
    • 1.3 重要方法
      • 1.3.1 启动一个线程 ---- start()
      • 1.3.2 中断一个线程 ---- interrupt()
      • 1.3.3 等待一个线程 ---- join()
      • 1.3.4 获取当前线程 ---- currentThread()
      • 1.3.5 休眠当前线程 ---- sleep()
  • 2. 线程的状态
    • 2.1 观察线程所有的状态
    • 2.2 线程状态和状态转移的意义
  • 总结


前言

本文是属于多线程初阶内容系列的, 如果还没有学习过之前文章的, 请先移步博主的之前的文章进行学习, 本文就是在学会线程的创建后, 再带大家认识一下 Thread 类以及其常见的方法, 再给大家讲解一下线程都有哪些状态.

关注收藏, 开始学习吧🧐


1. Thread 类及常见方法

1.1 常见构造方法

通过我们上篇文章的学习, 我们已经学会了如何创建一个线程, 创建线程的方式主要有两种, 一种是继承 Thread 类, 一种是实现 Runnable 接口, 分别对应着下面, 第一和第二种构造方法, 而第三第四种构造方法, 是在第一第二种的基础上, 加了一个 name 参数, 效果是在创建线程对象时, 可以给其进行命名.

五种常见构造方法(第五种了解即可):

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("我的线程");
Thread t4 = new Thread(new MyRunnable(), "我的线程");

利用 jconsole 给大家简单演示一下取了别名的效果.

public class ThreadDemo6 {

    public static void main(String[] args) {

        Thread t = new Thread( () -> {
            while (true) {
                System.out.println("hello t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "我的线程");
        
        t.start();
    }

}

在这里插入图片描述
可以看到, 在线程中有一个名为 “我的线程” 的线程, 是由我们自己命名的.

1.2 常见属性

每个线程, 都有自己的名称, 状态, 优先级, 上下文, 记账信息等等 (之前在讲到进程中这些属性时, 其实都是线程的, 只不过之前谈到的进程是属于只有一个线程的进程).

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted

ID, 名称, 状态, 优先级都比较好理解, 我们重点讲一下后台进程, 存活, 被中断的这三个属性.

  • ID 是线程的唯一标识, 不同线程不会重复.
  • 名称是各种调试工具需要用到的.
  • 状态表示线程当前所处的一个情况, 下面我们会进一步说明.
  • 优先级高的线程理论上来说更容易被调度到.
  • isDaemon() 是否后台线程: true 表示的是后台线程, false 表示的是前台线程. 那么前台线程和后台线程有什么区别呢? 后台线程不会阻止 Java 进程结束, 哪怕后台线程还没有执行完, Java 进程该结束时就结束了. 而前台线程会阻止 Java 进程结束, 必须得 Java 进程中的所有前台线程都执行结束后, Java进程才能结束. 我们创建的线程默认是前台的. 可以通过 setDaemon() 方法来设置成后台.
  • isAlive() 是否存活: 描述系统内核里的那个线程, 是否存活. 怎样判定线程是否存活呢? 线程的入口方法执行完毕, 此时系统中对应的线程就没了, 调用该方法结果就是 false.
  • isInterrupted 是否被中断: 描述系统内核里的那个线程, 是否被中断. 线程的中断问题, 下面我们进一步说明, 在之后的重要方法 interrupt() 中会重点讲述, 这里我们知道即可.

给大家看一个程序演示一下这些属性:

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还活着");
                    Thread.sleep(1 * 1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
       });
        System.out.println(Thread.currentThread().getName() 
                           + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName() 
                           + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName() 
                           + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName() 
                           + ": 优先级: " + thread.getPriority());
 		System.out.println(Thread.currentThread().getName() 
                           + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() 
                           + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName() 
                           + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName() 
                           + ": 状态: " + thread.getState());
   }
}

在这里插入图片描述

1.3 重要方法

下面我们重点介绍一下 Thread 类中的几个重要方法.

1.3.1 启动一个线程 ---- start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象, 但线程对象被创建出来并不意味着线程就开始运行了.

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法, 就是喊一声: “行动起来!” , 线程才真正独立去执行了
    在这里插入图片描述

这里也有一个很重要的问题需要大家思考一下, 这是一道很经典的面试题, 请说明Thread类中run和start的区别:

这里, 我们从方法的作用功能, 及运行结果的区别分别说明.

  • 作用功能不同:
    • run 方法的作用是描述线程具体要执行的任务.
    • start 方法的作用是真正的去申请系统线程.
  • 运行结果不同:
    • run 方法是一个类中的普通方法, 主动调用和调用普通方法一样, 会顺序执行一次.
    • start 调用方法后, start 方法内部会调用 Java 本地方法(封装了对系统底层的调用)真正的启动线程, 并执行 run 方法中的代码, run 方法执行完成后线程进入销毁阶段.

调用 start 方法, 才是真的在操作系统的底层创建出一个线程.

1.3.2 中断一个线程 ---- interrupt()

中断一个线程, 这里就是字面意思, 就是让一个线程停下来, 也就是线程的终止. 本质上来说, 让一个线程终止, 办法只有一种, 就是让该线程的入口方法执行完毕.

给大家再用上面的例子讲一下:

李四一旦进到工作状态, 他就会按照行动指南上的步骤去进行工作, 不完成是不会结束的. 但有时我们需要增加一些机制, 例如老板突然来电话了, 说转账的对方是个骗子, 需要赶紧停止转账, 那张三该如何通知李四停止呢? 这就涉及到我们的停止线程的方式了.

目前常见的让线程的入口方法结束的方式有以下两种:

  1. 通过共享的标记来进行沟通, 也就是给线程中设置一个共享标记位.
  2. 调用 Thread 类中的 interrupt() 方法来通知.

示例1: 使用自定义的变量来作为标志位.

public class ThreadDemo7 {

    // 设置标志位
    public static boolean isQuit = false;

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!isQuit) {
                System.out.println("还没结束");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            System.out.println("我结束啦");
        });

        // 开启一个thread线程
        thread.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // 3s 后, 在主线程中修改标志位
        isQuit = true;
    }

}

在这里插入图片描述
示例2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.

上面我们是使用自己创建的变量来控制循环, 而 Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.

方法说明
public void interrupt()中断对象关联的线程, 如果线程正在阻塞, 则以异常方式通知, 否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置, 调用后清除标志位
public boolean isInterrupted()判断对象关联的线程的标志位是否设置, 调用后不清除标志位

使用 thread 对象的 interrupted() 方法通知线程结束.

public class ThreadDemo8 {

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
        	// 两种方法均可以
            while (!Thread.interrupted()) {
//            while (Thread.currentThread().isInterrupted()) {
                System.out.println("还没结束");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 注意此处的 break
                    break;
                }
            }
            System.out.println("我结束啦");
        });

        t.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

		// 把 t 内部的标志位给设置成 true
        t.interrupt();
    }

}

thread 收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起, 则以 InterruptedException 异常的形式通知, 清除中断标志.
    • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以利用 break 跳出循环结束线程.
  2. 否则, 只是内部的一个中断标志被设置, Thread 可以通过
    • Thread.interrupted() 判断当前线程的中断标志被设置, 清除中断标志.
    • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置, 不清除中断标志.

这种方式通知收到的更及时, 即使线程正在 sleep 也可以马上收到.

示例3: 观察标志位是否清除

标志位是否清除, 就类似于一个开关.

  • Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
  • Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为 “不清除标志位”.
  1. 使用 Thread.interrupted() , 线程中断会清除标志位.
public class ThreadDemo9 {

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.interrupted());
            }
        }, "慧天城");

        t.start();

        t.interrupt();
    }

}

可以看到, 输出结果中, 只有一开始是 true, 剩下都是 false, 因为标志位被清除了.
在这里插入图片描述

  1. 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.
public class ThreadDemo10 {

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().isInterrupted());
            }
        });

        t.start();
        t.interrupt();
    }

}

结果全部都是 true, 因为标志位没有被清除.
在这里插入图片描述

1.3.3 等待一个线程 ---- join()

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束, 最多等 millis 毫秒
public void join(long millis, int nanos)同理, 但可以更高精度

线程之间是并发执行的, 操作系统对于线程的调度, 是无序的. 无法判断两个线程谁先执行结束, 谁后执行结束.

有时, 我们需要等待一个线程完成它的工作后, 才能进行自己的下一步工作.

看下面这段代码:

public class ThreadDemo11 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello t");
        });

        t.start();

        System.out.println("hello main");
    }
}

这段代码是先输出 hello main 还是 hello t 呢? 这是无法确定的, 虽然这个代码实际执行时, 大部分情况下都是先出 hello main, 但是也不能排除特定情况下, 主线程 hello main 还没有执行到, 先输出 hello t 的情况也会发生.

对于我们程序员来说, 这种情况是不能发生的, 因为我们需要有一个明确的执行顺序, 来确保程序不会出现 bug, 这时我们就可以使用线程等待来实现, 也就是 join() 方法.

public class ThreadDemo11 {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("hello t");
        });

        t.start();

		// 使用join方法
        t.join();
        
        System.out.println("hello main");
    }

}

在 t.join() 执行时, 如果 t 线程还没有结束, main 线程就会阻塞等待, 代码都到这一行就会先停下来, 暂时不参与 CPU 的调度执行了.

在这里, 我们是在 main 线程中, 调用 t.join(), 意思就是让 main 线程先等待 t 线程结束, 再继续往下执行, 注意是只有 main 线程进入阻塞, 其余线程均不受影响.

补充t.join();

  1. main 线程在调用 t.join 的时候, 如果 t 线程还在运行, 此时, main 线程堵塞, 直到 t 执行完毕后( t 的 run 执行结束后), main 才从阻塞中解除, 才继续执行.
  2. main 线程调用 t.join 的时候, 如果 t 线程已经结束了, 此时 join 不会阻塞 main 线程, 就会立即往下执行.

但无论哪种情况, 都可以保证 t 线程是最先结束的那个

join 方法还有别的版本, 可以填写参数, 作为 “超时时间” (等待的最大时间).
在这里插入图片描述

  • join 的无参数版本, 效果就是 “死等”, 等待的那个进程什么时候结束, 就什么时候继续往下执行.
  • join 的有参数版本, 则是指定最大超过时间, 如果等待的时间到了上限, 还没等到, 就不等了.

1.3.4 获取当前线程 ---- currentThread()

这个方法, 我们应该就非常熟悉了, 这里也不过多讲解了.

方法说明
public static Thread currentThread();返回当前线程对象的引用
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

1.3.5 休眠当前线程 ---- sleep()

这个也是我们比较熟悉一组方法, 有一点要记得, 因为线程的调度是不可控的, 所以, 这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的.

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException可以更高精度的休眠
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3 * 1000);
        System.out.println(System.currentTimeMillis());
   }
}

2. 线程的状态

操作系统中的线程, 自身是有一个状态的, 在 Java Thread 中进行了对系统线程的进一步封装, 于是把这里的状态又进一步的精细化了.

2.1 观察线程所有的状态

线程的状态是一个枚举类型 Thread.State

public class ThreadState {
    public static void main(String[] args) {
    	// 查看线程所有的状态
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
       }
   }
}

在这里插入图片描述

  • NEW: 安排了工作, 还未开始行动, 代表系统中的线程还没创建出来, 只是有了个 Thread 对象.
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
  • BLOCKED: 表示排队等着其他事情, 等待锁出现的状态.
  • WAITING: 表示排队等着其他事情, 使用 wait 方法出现的状态.
  • TIMED_WAITING: 表示排队等着其他事情, 指定时间等待 sleep 方法.
  • TERMINATED: 工作完成了, 系统中的线程执行完了, Thread 对象也还在.

2.2 线程状态和状态转移的意义

在这里插入图片描述
大家不要被这个状态转移图吓到, 我们重点是要理解状态的意义以及各个状态的具体意思.
在这里插入图片描述
还是我们之前的例子:

刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态.
当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度.
当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入 BLOCKEDWATINGTIMED_WAITING 状态. 这些状态有什么区别, 我们之后再进行了解. 如果李四、王五已经忙完,为 TERMINATED 状态.
所以,之前我们学过的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的.


总结

✨ 本文主要讲解了 Thread 类及常见方法以及线程的状态是什么, 举了一些通俗的例子来帮助大家理解. 重点需要掌握 Thread 类中的一些重要方法, 以及 start 方法和 run 方法的区别, 还有线程都有哪些状态.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.

再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有PMP有没有必要换cspm?未来的发展前景如何?

最近 CSPM 证书很热门&#xff0c;CSPM 相关问题大家都很关心&#xff0c;今天本橘座就给大家全面解答一下 CSPM到底是何方神圣&#xff1f; 文章主要是解答下面几个常见问题&#xff0c;其他问题可以留言或者私信咨询我哦~ 一、什么是 CSPM证书&#xff1f;跟PMP是什么关系&a…

启动、关闭nacos

下载 进入官网 http://nacos.io/zh-cn/ 页面打开后&#xff0c;点击【前往Github】 进入Github页面&#xff0c;往下拖动&#xff0c;找到 latest stable release 选择当时最新的版本下载即可&#xff0c;这里选择.zip的文件下载 安装 Nacos 是免安装的&#xff0c;我们下…

备战秋招008(20230713)

文章目录 前言一、今天学习了什么&#xff1f;二、关于问题的答案1.集合2.JUC02、底层原理03、内存泄漏 总结 前言 提示&#xff1a;这里为每天自己的学习内容心情总结&#xff1b; Learn By Doing&#xff0c;Now or Never&#xff0c;Writing is organized thinking. 目前…

python - leetcode - 64. 最小路径和【经典题解 - 矩阵数组动态规划】

一. 题目&#xff1a;64. 最小路径和 描述&#xff1a; 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#…

【SpringBoot3】--02.Web开发

文章目录 SpringBoot3-Web开发0.WebMvcAutoConfiguration原理1.生效条件2.效果3.WebMvcConfigurer接口4. 静态资源规则源码5. EnableWebMvcConfiguration 源码6. 为什么容器中放一个WebMvcConfigurer就能配置底层行为7. WebMvcConfigurationSupport 1.Web场景1.1自动配置1.2默认…

51单片机-串口通信(串口向电脑发送信息电脑通过串口控制LED)

文章目录 前言一、串行通信口的功能以及串行通信口的结构及原理1.1 串行通信口的功能1.2 51单片机串口的结构 二、串行通信口的控制寄存器2.1 串行控制寄存器SCON2.2 电源控制寄存器PCON2.3 配置寄存器配置SCON寄存器配置PCON寄存器配置中断 2.4 实验单片机向pc发送数据PC向单片…

文心一言 VS 讯飞星火 VS chatgpt (58)-- 算法导论6.4 2题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;58&#xff09;-- 算法导论6.4 2题 二、试分析在使用下列循环不变量时&#xff0c;HEAPSORT 的正确性&#xff1a;在算法的第 2~5行 for 循环每次迭代开始时&#xff0c;子数组 A[1…i]是一个包含了数组A[1…n]中第i小元素的最大…

[疑难杂症2023-005]dcmtk-movescu访问dcm4chee模拟PACS Server报错问题解析

本文由Markdown语法编辑器编辑完成。 1. 背景 由于要在医院上线一个服务&#xff0c;用来定时从医院的PACS Server上面拉取dicom图像并进行分发。因此&#xff0c;需要首先在公司的内网环境上进行开发&#xff0c;测试和验证。 目前比较流行和开源的PACS Server模拟是基于dc…

python 面向对象编程(2)

文章目录 前言封装多态类属性和实例属性定义以及访问类属性修改类属性实例属性 类方法静态方法 前言 前面我们介绍了 python 类和对象以及继承、私有权限&#xff0c;那么今天我们将来介绍 python面向对象 剩下的两大特性封装、多态&#xff0c;以及如何访问、修改类属性、类方…

机器学习李宏毅学习笔记37

文章目录 前言一、ChatGPT做的事情二、ChatGPT基础介绍三、ChatGPT带来的研究问题总结 前言 ChatGPT简单原理介绍 一、ChatGPT做的事情 Chatgpt以句子为输入&#xff0c;输出这个句子后面接的词汇的概率分布&#xff0c;给每一个可能的符号一个概率&#xff0c;根据这个分布进…

【雕爷学编程】Arduino动手做(160)---HLK-V20离线语音模块2

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

RabbitMQ知识掌握 【进阶篇】

一、如何保证消息的可靠性 &#x1f349; 1.保证消息的可靠性投递 &#x1f95d; 在生产环境中由于一些不明原因&#xff0c;导致 rabbitmq 重启&#xff0c;在 RabbitMQ 重启期间生产者消息投递失败&#xff0c;导致消息丢失&#xff0c;需要手动处理和恢复。于是&#xff0…

微信小程序活动抽奖简单实现,包教包会

问题&#xff1a; 针对用户使用活动抽奖&#xff0c;获得抽奖得到商品的成就以及满足感&#xff0c;那么我们应该怎么去实现小程序去转盘抽奖活动呢 例如&#xff1a;项目需要抽奖实现相应的奖品奖励 实现方法 实现的效果如下&#xff1a; 实现的主要代码&#xff1a; Page…

ANSYS Workbench项目分析与案例实操详解

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言准备工作创建项目导…

盘点5款Cesium三维GIS渲染引擎

目前&#xff0c;以Cesium为核心的各类产品繁多&#xff0c;本文将挑选一些以Cesium为核心的软件案例&#xff0c;为大家进行介绍。 1. CesiumJS CesiumJS相信凡是GIS行业相关人员都特别熟悉了&#xff0c;CesiumJS是一款开源的JavaScript库&#xff0c;用于创建高性能的地球…

以太网(Ethernet)入门了解

以太网&#xff08;Ethernet&#xff09;是一种常见的局域网&#xff08;LAN&#xff09;通信协议&#xff0c;它是由Xerox公司于1970年代中期开发的。以太网是一种基于广播技术的开放式网络协议&#xff0c;它允许设备在共享通信介质上进行通信。以下是关于以太网的基本概念、…

如何做到有效的项目质量管理?这三点很关键

“项目管理中&#xff0c;大家都遇到过的哪些绊脚石&#xff1f;” 近期看到了这样一个话题&#xff0c;大家纷纷吐槽了在项目中遇到的各种困难。除了常见的范围无限蔓延、进度不停延误、成本严重超支、内部沟通困难等一系列问题&#xff1b;其中提到次数较多的还有项目的质量…

QOwnNotes Mac版-QOwnNotes for Mac(事务笔记管理)

QOwnNotes for Mac是一款Mac平台上的笔记记录和待办事项软件&#xff0c;可以将你的笔记保存为纯文本文件&#xff0c;它支持 MarkDown 支持&#xff0c;并与 ownCloud 云服务紧密集成。 QOwnNotes 的亮点就是它集成了 ownCloud 云服务&#xff08;当然是可选的&#xff09;。…

JavaWeb 速通XML

目录 一、XML快速入门 1.基本介绍 : 2.入门案例 : 二、XML语法 0.文件结构 : 1.文档声明 &#xff1a; 2. 元素 : 3.属性 : 4.注释 : 5.CDATA节 : PS : XML转义符 : 三、Dom4j 1.关于XML解析技术 : 2 Dom4j介绍 : 3.Dom4j使用 : 1 获取Document对象的三种方式 2 …

ROS:Gazebo详解

目录 一、前言二、URDF与Gazebo基本集成流程2.1创建功能包2.2编写URDF文件2.3启动Gazebo并显示模型 三、URDF集成Gazebo相关设置3.1collision3.2inertial3.3颜色设置 四、URDF集成Gazebo实操4.1需求4.2流程4.3编写封装惯性矩阵算法的 xacro 文件4.4底盘 Xacro 文件4.5摄像头 Xa…