并发编程笔记7--并发编程基础

1、线程简介

1.1、什么是线程

现代操作系统中运行一个程序,会为他创建一个进程。而每一个进程中又可以创建许多个线程。现代操作系统中线程是最小的调度单元。
两者关系:一个线程只属于一个进程,而一个进程可以拥有多个线程。线程是一个轻量级的进程
一个Java程序从main方法开始执行,然后按照既定的顺序执行代码,看上去没有其他线程参与。但实际上Java程序天生就是多线程程序。我们可以通过JMX来查看一个普通的Java程序包括哪些线程。示例代码如下:

public class MultiThread {

    public static void main(String[] args) {
        ThreadMXBean threadMXBean= ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos=threadMXBean.dumpAllThreads(true,true);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName());
        }
    }
}

上面代码执行结果如下:
image.png
从上图可以看到,一个Java程序运行不仅是main方法的运行,而是main进程和一些其他进程的同时运行。

1.2、为什么要使用多线程

  1. 更多的处理器核心:现代计算机大多是多核处理器,一个程序作为一个进程在处理器上运行。程序运行过程中可以创建多个线程,而一个线程只能在一个处理器核心上。试想一下,一个单线程程序在运行时只能使用一个处理器核心,那么在多的处理器核心加入也不会提升程序的运行效率。相反如果采用多线程技术,那么运行在多核处理器上就会显著提升运行效率。
  2. 更快的相应时间
  3. 更好的编程模型

**

1.3、线程优先级

在《Java并发编程的艺术》的一书中,作者的实验代码的结果得出的结论是线程的优先级不能作为程序正确性的依赖,操作系统完全可以不用理会Java代码对优先级的设置(作者环境为jdk1.7+Mac OS X10.10)。但是我自己跑出的结果可以看到代码中设置的优先级是有用的(本人环境jdk1.8+win10)。实例代码如下:

public class Priority {

    private static volatile boolean notStart=true;
    private static volatile boolean notEnd=true;

    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs=new ArrayList<>();

        for (int i=0;i<10;i++){
            int priority=i<5?Thread.MIN_PRIORITY:Thread.MAX_PRIORITY;
            Job job=new Job(priority);

            jobs.add(job);

            Thread thread=new Thread(job,"Thread:"+i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart=false;
        TimeUnit.SECONDS.sleep(10);
        notEnd=false;
        for (Job job:jobs){
            System.out.println("Job priority:"+job.priority+" count:"+job.jobCount);
        }
    }

    static class Job implements Runnable{

        private int priority;
        private long jobCount;
        Job(int priority){
            this.priority=priority;
        }


        @Override
        public void run() {
            while (notStart){
                Thread.yield();
            }
            while (notEnd){
                Thread.yield();
                jobCount++;
            }
        }
    }
}

本人跑出的结果为:
image.png

从上图可以看到优先级为10的结果和优先级为1的结果差距是挺大的。至于为什么会和作者的结论不一样,尚未搞清楚。

1.4、线程的状态

线程在运行过程中可能处于以下6种状态中的某一个,在给定的一个时刻,线程只能处于其中的一个状态。

状态名称说明
NEW初始状态,线程被构建,但是还未调用start方法
RUNNABLE运行状态,线程调用start方法后,Java线程将操作系统中的就绪和运行两种状态笼统的成为运行中
BLOCKED阻塞状态,表示线程阻塞与锁
WAITING等待状态,进入该状态的线程需要等待其他线程做出一些特定的动作(通知,中断)
TIME_WAITING超时等待,表示线程等待指定的时间之后自动唤醒,继续执行
TERMINATED终止状态,表示当前线程已经执行完毕

我们可以通过jstack命令来查看线程的执行状态。示例代码如下:

public class ThreadState {

    //改该程一直睡眠
    static class TimeWaiting implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //该线程一直等待
    static class Waiting implements Runnable{

        @Override
        public void run() {
            while (true){
                synchronized (Waiting.class){
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class Blocked implements Runnable{

        @Override
        public void run() {
            synchronized (Blocked.class){
                while (true){
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new TimeWaiting(),"TimeWaitingThread").start();
        new Thread(new Waiting(),"WaitingThread").start();
        new Thread(new Blocked(),"BlockedThread-1").start();
        new Thread(new Blocked(),"BlockedThread-2").start();
    }
}

1、先通过jps命令来查看线程pid
2、通过jstack pid来查看线程的状态

image.png
image.png
image.png
image.png

java线程变迁图如下:
image.png
从上图可以看到,当线程被创建之后,执行start方法之后线程处于运行状态。当线程执行wait方法之后,线程处于等待状态,这时此线程需要其他线程唤醒才能继续执行(notify和notifyAll)。而超时等待状态是在等待状态的基础上加了等待超时机制,也就是说线程在等待指定的时间后,就会自动回到执行状态,不需要其他线程唤醒。当线程调用同步方法时,在没有获取到锁的情况下,线程会被阻塞住,此时线程处于阻塞状态。当线程执行run方法之后,就处于终止状态了。
阻塞状态是线程阻塞在进入由synchronize修饰的方法或者代码块时的状态。但是阻塞状态在java.concurrent包中的Lock接口上的状态是等待状态。那是因为java.concurrent包中Lock接口对阻塞的实现均使用了LockSupport类中的方法。

1.5、Daemon线程

Daemon线程是一种支持型线程,主要用作程序中后台调度以及支持性工作。在JVM中,当不存在非Daemon线程时,JVM将会退出。
Daemon线程时支持型线程,当JVM退出时,Daemon线程中的finally块并不一定会执行。实例代码如下:

public class Daemon {

    static class DaemonRunner implements Runnable{

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println("DaemonThread finally run.");
            }
        }
    }

    public static void main(String[] args) {
        Thread thread=new Thread(new DaemonRunner(),"DaemonThread");
        thread.setDaemon(true);
        thread.start();
    }
}

如果是Daemon线程执行结果如下:
image.png
非Daemon线程执行结果如下:
image.png

PS:在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

2、线程的启动和终止

2.1、线程的启动

线程的启动通过调用线程的start方法来启动。start方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start方法的线程。
PS:启动一个线程前,最好设置线程的名称,这样,在使用jstack排查程序问题时,就会给开发人员提供一些提示。自定义线程最好能够起个别名。

2.2、线程的中断

中断可以理解为线程的一个标识位属性。他表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否中观来进行响应,线程通过isInterrupted来进行是否中断判断,也可以调用静态方法Thread.interrupted对当前线程的中断标识位进行复位。但是如果线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted时,依旧会返回false。
在Java的许多API中,有许多抛出InterruptedException的方法,这些方法在抛出InterruptedException之前,JVM会先将线程中的中断标识位清除,这是调用该线程对象的isInterrupted时,依旧会返回false。
示例代码如下:

public class Interrupted {


    static class SleepRunner implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class BusyRunner implements Runnable{

        @Override
        public void run() {
            while (true){

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread sleepThread=new Thread(new SleepRunner(),"SleepRunner");
//        sleepThread.setDaemon(true);

        Thread busyThread=new Thread(new BusyRunner(),"BusyRunner");
//        busyThread.setDaemon(true);

        sleepThread.start();
        busyThread.start();

        TimeUnit.SECONDS.sleep(5);

        sleepThread.interrupt();
        busyThread.interrupt();

        System.out.println("SleepThread interrupted is"+ sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is"+ busyThread.isInterrupted());

        TimeUnit.SECONDS.sleep(2);
    }
}

运行结果如下:
image.png
从结果中可以看到抛出异常的SleepThread线程在抛出后,其标识位被清楚了返回的是false,而一直运行的BusyThread线程返回是true。

2.3、过期的suspend,resume,stop

1、suspend():暂定线程
2、resume():恢复线程
3、stop():终止线程

过期的原因:以suspend方法为例,在调用后,线程不会释放占用的资源(比如锁),而是占用着资源进行睡眠,这时就有可能导致死锁。同样stop方法在终止线程时,不会保证线程的资源正常释放,通常是没有给予线程完成资源释放的机会。

2.4、安全的终止线程

中断是一种简便的线程间的交互方式,而这种方式最适合用来取消或停止任务。当然还可以用共享变量来终止任务。示例代码如下:

public class Shutdown {

    static class Runner implements Runnable{
        private long i;
        private volatile boolean on =true;
        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()){
                i++;
            }

            System.out.println(i);
        }

        public void cancel(){
            on=false;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runner one=new Runner();
        Thread thread=new Thread(one,"one");
        thread.start();

        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();

        Runner weo=new Runner();
        Thread thread1=new Thread(weo,"weo");
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        weo.cancel();
    }
}

运行结果如下:
image.png
在示例中,main方法中通过中断操作和cancel方法均可使线程终止。这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源而不是武断的将线程终止。

3、线程间的通信

3.1、volatile和synchronize

java允许多线程同时访问同一个共享变量。由于每一个线程都有这个变量的拷贝(JMM协议),所以线程中看到的变量不一定是最新的。
关键字volatile可以用来修饰变量,就是用来告知程序对这个变量的访问要从内存中取,对这个变量的修改要立即写到刷新到内存中,这样其他线程每次看到的都是最新的数据。volatile详情
关键字synchronize可以修饰方法或者代码块,他用来保证同一时刻只能有一个线程能进入方法或者同步代码块中。他保证了线程对变量访问的排他性和可见性。synchronize详情

3.2、等待/通知机制

一个线程修改了一个值,另一个线程感知到变化,然后进行相关的操作。整个过程开始于一个线程,而最终执行与另一个线程。前者是生产者,后者就是消费者。这种结构在功能层面上实现了解耦,体系结构具有良好的伸缩性。那么在java中要如何实现呢?
在java中简单的办法就是让消费者线程循环检查变量是否符合预期。如下面的伪代码,在while循环中设置不满足的条件,如果条件允许则退出while循环,从而完成消费的工作。示例代码如下:

while(value != desire){
	Thread.sleep(1000);
}
doSomeThing();

上面这段代码,条件不足时就睡眠一段时间,这样做的目的是防止过快的无效尝试,浪费CPU的资源。这种方式看似能解决问题,但是却存在以下问题:

  1. 难以确保及时性
  2. 难以降低开销

对于以上问题,Java中内置了等待/通知机制。等待/通知相关方法是任意Java对象都具备的,因为其定义在所有对象的超类java.lang.Object上。相关方法描述如下:

方法名称描述
notify()通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到该对象的锁。
notifyAll()通知所有等待在该对象上的线程
wait()调用该方法的线程进入WAITING状态,只有等待另外线程的通知或者中断才能返回。在调用wait方法后,会释放对象的锁。
wait(long)超时等待一段时间,参数是毫秒,也就是等待长达n毫秒后,会没有通知就可以超时返回。调用该方法的线程会进入TIMED_WAITING状态
wait(long,int)对于超时等待更细粒度的控制,可以达到纳秒

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程基于对象O来完成交互。对于wait,notify,notifyAll的使用示例代码如下:

public class WaitNotify {

    static boolean flag=true;
    static final Object lock=new Object();

    static class Wait implements Runnable{

        @Override
        public void run() {
            synchronized (lock){
                while (flag){
                    System.out.println(Thread.currentThread()+" flag is true.wait "+ LocalDateTime.now());
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(Thread.currentThread()+" flag is false. running "+LocalDateTime.now());
            }
        }
    }

    static class Notify implements  Runnable{

        @Override
        public void run() {
            synchronized (lock){
                System.out.println(Thread.currentThread()+" hold lock. notify "+LocalDateTime.now());
                lock.notify();
                flag=false;
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            synchronized (lock){
                System.out.println(Thread.currentThread()+" hold lock again. sleep"+LocalDateTime.now());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread=new Thread(new Wait(),"WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);

        Thread notify=new Thread(new Notify(),"NotifyThread");
        notify.start();
    }
}

执行结果如下:
image.png
从上述的例子中,有以下使用wait,notify,notifyall的细节:

  1. 调用wait、notify、notifyAll需要先对调用对象加锁
  2. 调用wait方法之后,线程状态由RUNNING变为WAITING,并将当前线程放到对象上的等待队列中。
  3. notify、notifyAll方法调用之后,等待线程依旧不会从wait返回,需要调用notify、notifyAll的线程释放锁之后,等待线程才有机会从wait返回。
  4. notify方法将等待队列中的一个线程从等待队列移动到同步队列中,notifyAll方法则是将等待队列中所有的线程全部移动到同步队列中。被移动的线程从WAITING变为BLOCKED。
  5. 从wait方法返回的前提是从对象获取到锁。

上述代码运行过程如下:
image.png
在图中,首先WaitThread获取到对象的锁,然后调用wait方法,从而放弃了锁,并进入等待队列中。由于WaitThread释放了锁,NotifyThead线程获取到对象的锁,并调用了线程的notify方法,将WaitThread从等待队列中转移到同步队列中,此时WaitThread线程的状态变为阻塞状态,在NotifyThread线程释放锁之后,WaitThread再次获取到锁,并从wait方法返回继续执行。

3.3、等待/通知经典范式

我们可以从上述示例代码WaitNotify中提炼出等待/通知的经典范式。改范式分为两部分,分别针对等待方(消费者)和通知方(生产者)。
等待方(消费者)遵循的原则如下:

  1. 获取对象的锁
  2. 如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件
  3. 条件满足继续执行对应的逻辑

对应的伪代码如下:

synchronize(对象){
	while(条件不满足){
  	对象.wait();
  }
  对应的处理逻辑
}

通知方(生产者)遵循的原则如下:

  1. 获取对应的锁
  2. 改变条件
  3. 通知所有等待在对象上的线程

对应的伪代码如下:

synchronize(对象){
	改变条件;
 	对象.notifyAll();
}

4、管道输入/输出流

管道输入/输出流和普通输入/输出流或者网络输入/输出流的区别在于,管道输入输出流主要用于线程间的数据传输,而传输的媒介就是内存。
管道输入输出流的实现主要分为PipedOutputStream、PipedInputStream、PipedReader、PipedWriter,前两种面向字节,后两种面向字符。示例代码如下:

public class Piped {

    static class Print implements Runnable{
        private PipedReader in;

        public Print(PipedReader in){
            this.in=in;
        }

        @Override
        public void run() {
            int receive=0;

            while (true) {
                try {
                    if ((receive=in.read())!=-1){
                        System.out.print((char)receive);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        PipedWriter out=new PipedWriter();
        PipedReader in=new PipedReader();
        out.connect(in);
        Thread printThread=new Thread(new Print(in),"PrintThread");
        printThread.start();
        int receive=0;
        while (true) {
            try {
                if ((receive=System.in.read())!=-1) {
                    out.write(receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果如下:
image.png
在示例中,创建PringThread线程用来接受main方法的输入,任何main方法线程的输入均通过PipedWriter写入,而PringThread线程的另一端通过PipedReader将内容读出并打印。
对于pip流必须先进行绑定,也就是调用connect()方法,如果没有将输入/输出流绑定起来,那么对于流的访问会抛出异常。

5、Thread.join()的使用

如果一个线程A执行了thread.join()语句,其含义就是线程A等待线程thread执行完之后从thread.join()返回,并继续执行自己的代码。线程Thread除了提供join()方法外,还有join(long millis)和join(long millis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在指定的超时时间内没有终止,那么将会从thread.join方法中返回。示例代码如下:

public class Join {

    static class Domino implements Runnable{

        private Thread thread;

        public Domino(Thread thread){
            this.thread=thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+" terminate.");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread pre=Thread.currentThread();

        for (int i=0;i<10;i++){
            Thread thread=new Thread(new Domino(pre),String.valueOf(i));
            thread.start();
            pre=thread;
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }
}

执行结果如下:
image.png
从上述输出可以看到,每个线程的终止,都依赖于前一个线程的终止。

6、ThreadLocal的使用

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的值。
可以通过set(T)方法来设置一个值,在当前线程下在通过get()方法获取到原先设置的值。示例代码如下:

public class Profiler {

    private static final ThreadLocal<Long> TIME_THREAD_LOCAL=new ThreadLocal<Long>(){
        protected Long init(){
            return System.currentTimeMillis();
        }
    };

    public static void begin(){
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
    }

    public static long end(){
        return System.currentTimeMillis()-TIME_THREAD_LOCAL.get();
    }

    public static void main(String[] args) throws InterruptedException {
        Profiler.begin();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Cost: "+Profiler.end()+" mills");
    }
}

运行结果如下:
image.png

ThreadLocal结构

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试基础05:软件测试的分类

课程大纲 1、两种架构&#xff08;Architecture&#xff09; 1.1、B/S&#xff08;Browser/Server&#xff09; 浏览器服务器架构&#xff08;大体3步&#xff09;&#xff1a;用户通过浏览器向服务器发出请求&#xff0c;服务器处理请求&#xff0c;将结果通过网络返回到用户…

【数据挖掘】四分位数识别数据中的异常值(附代码)

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…

口碑比较好的相亲交友平台有哪些?正规靠谱的相亲软件排行榜测评

在网络时代&#xff0c;越来越多的人热衷于使用相亲交友软件来寻找生命中的另一半。这些软件确实为许多用户提供了真实可靠的交友平台。然而&#xff0c;市面上的相亲软件种类繁多&#xff0c;质量良莠不齐&#xff0c;让人难以选择。今天&#xff0c;我将介绍几款我使用过且认…

【ARM 裸机】按键输入

本节学习按键输入&#xff0c;先拷贝上一节工程文件&#xff0c; 1、驱动编写 新建 key 的 .h 和 .c 文件&#xff1b; 再查看一下硬件原理图如下&#xff1b; 由此可知&#xff0c;KEY0 按键接在 UART1_CTS 引脚上&#xff0c;默认情况下为高电平&#xff0c;按键按下为…

【LeetCode】30.串联所有单词的子串

串联所有单词的子串 题目描述&#xff1a; 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd",&qu…

超值分享50个DFM模型格式的素人直播资源,适用于DeepFaceLive的DFM合集

50直播模型&#xff1a;点击下载 作为直播达人&#xff0c;我在网上购买了大量直播用的模型资源&#xff0c;包含男模女模、明星脸、大众脸、网红脸及各种稀缺的路人素人模型。现在&#xff0c;我将这些宝贵的资源整理成合集分享给大家&#xff0c;需要的朋友们可以直接点击下…

工业路由器在工厂数字化的应用及价值

随着科技的飞速发展&#xff0c;数字化转型已成为工厂提高效率、降低成本、实现智能化管理的关键途径。在这个过程中&#xff0c;工业路由器凭借其独特的优势&#xff0c;正逐渐成为工厂数字化建设不可或缺的核心组件。本文将深入探讨工业路由器在工厂数字化中的应用及价值&…

c# 画一个正弦函数

1.概要 c# 画一个正弦函数 2.代码 using System; using System.Drawing; using System.Windows.Forms;public class SineWaveForm : Form {private const int Width 800;private const int Height 600;private const double Amplitude 100.0;private const double Period…

光电直读抄表技术详细说明

1.技术简述 光电直读抄表是一种智能化智能计量技术&#xff0c;主要是通过成像原理立即载入电度表里的标值&#xff0c;不用人工干预&#xff0c;大大提升了抄表效率数据可靠性。此项技术是智慧能源不可或缺的一部分&#xff0c;为电力公司的经营管理提供了有力的适用。 2.原…

2024年5月26日 十二生肖 今日运势

小运播报&#xff1a;2024年5月26日&#xff0c;星期日&#xff0c;农历四月十九 &#xff08;甲辰年己巳月庚寅日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;马、猪、狗 需要注意&#xff1a;牛、蛇、猴 喜神方位&#xff1a;西北方 财神方位&#xff1a;…

基于open3d加载kitti数据集bin文件

前言 在自动驾驶领域&#xff0c;Kitti数据集是一个非常流行的点云数据集&#xff0c;广泛用于3D目标检测、跟踪和其他相关研究。Open3D是一个强大的开源库&#xff0c;专门用于处理和可视化三维数据。本文将介绍如何使用Open3D来加载和可视化Kitti数据集中的.bin文件。 准备…

marimo,Python notebook 的未来

你好&#xff0c;我是坚持分享干货的 EarlGrey&#xff0c;翻译出版过《Python编程无师自通》、《Python并行计算手册》等技术书籍。 如果我的分享对你有帮助&#xff0c;请关注我&#xff0c;一起向上进击。 marimo&#xff0c;号称是下一代 Jupyter Notebook&#xff0c;是 P…

长文处理更高效:一键章节拆分,批量操作轻松搞定,飞速提升工作效率!

在当今信息爆炸的时代&#xff0c;我们时常需要处理大量的文本内容。无论是阅读长篇小说、整理专业资料还是编辑大型文档&#xff0c;TXT文本文件的普遍性不言而喻。然而&#xff0c;当TXT文本内容过于庞大时&#xff0c;阅读、编辑和管理都变得异常繁琐。此时&#xff0c;一款…

echarts-树图、关系图、桑基图、日历图

树图 树图主要用来表达关系结构。 树图的端点也收symbol的调节 树图的特有属性&#xff1a; 树图的方向&#xff1a; layout、orient子节点收起展开&#xff1a;initialTreeDepth、expandAndCollapse叶子节点设置&#xff1a; leaves操作设置&#xff1a;roam线条&#xff1a…

eNSP学习——OSPF单区域配置

目录 相关命令 实验背景 实验目的 实验步骤 实验拓扑 实验编址 实验步骤 1、基础配置 2、部署单区域OSPF网络 3、检查OSPF单区域的配置结果 OSPF——开放式最短路径优先 基于链路状态的协议&#xff0c;具有收敛快、路由无环、扩展性好等优点&#xff1b; 相关命令 […

电信光猫的USB存储对外网开放访问

前提条件当然是要有公网IP地址了&#xff0c;没有的话去找电信索要&#xff0c;然后可以使用动态域名正常访问。 我的电信光猫发现共享访问速度还可以&#xff0c;会有31M/s左右的写入速度 但是有一个不方便的是&#xff0c;无法从外网提供访问&#xff0c;SMB协议所用的445端…

军队仓库管理系统|DW-S301系统特点

部队仓库管理系统DW-S301系统通过数据采集、互联网和物联网技术&#xff0c;实现数字化智能管控&#xff0c;以提高军用物资的仓储准确率和流转率&#xff0c;缩短周转时间&#xff0c;降低库存成本&#xff0c;也有助于消除生产过程中的不确定性。 系统功能&#xff1a;通过部…

WebService的wsdl详解

webservice服务的wsdl内容详解&#xff0c;以及如何根据其内容编写调用代码 wsdl示例 展示一个webservice的wsdl&#xff0c;及调用这个接口的Axis客户端 wsdl This XML file does not appear to have any style information associated with it. The document tree is shown…

DSVPN综合实验

DSVPN综合实验 一.实验拓扑 二.实验要求 1&#xff0c;R5为ISP&#xff0c;&#xff0c;只能进行IP地址配置;其所有地址均配为公有IP地址 2&#xff0c;R1和R5间使用ppp的PAP认证&#xff0c;R5为主认证方; R2于R5之间使用ppp的chap认证&#xff0c;R5为主认证方&#xff0c;…

python web自动化(Allure报告)

Allure详细安装请看之前的博客 1.Allure配置与⼊⻔ 运⾏⽤例&#xff0c;⽣成allure报告 pip install allure-pytest -i https://mirrors.aliyun.com/pypi/simple/ 运⾏⽤例&#xff0c;⽣成allure报告 # main.py import os import pytest if __name__ __m…