Java多线程:创建多线程的“四种“ 方式

news2025/1/16 21:14:46

Java多线程:创建多线程的"四种" 方式

在这里插入图片描述


每博一文案

白马笑西风写道:江南有杨柳,有燕子,金鱼......汉人中有的是英俊勇武的少年,倜傥潇洒的少年......
但这个美丽的姑娘就像故高昌国人那样固执:"那都是很好很好的,可是我偏不喜欢"。
谁都被这样义无反顾的选择吧,不是畏首畏尾,不是瞻前顾后,更不是权衡利弊的审视,而是下意识的惦记和牵挂,
是义无反顾的选择。这个世界上没有人有义务堆你好,所以你更应该,这样相信哪些冷漠和刻薄是理所当然的,
哪些温柔相待更应该珍惜,如果总是相信下一个会更好。那么,往往遇见的会一个比一糟糕,经历了越多人就是
容易拿来对比,也就越来越不相信真心。
所以,不要因为一件东西好,你才千方百计去拥有他,而是因为你已经拥有了它,才一心一意觉得
他最好,听过这样一句话:你若不懂得珍惜,我又何必牵挂,人缘之间的关系是流动的?
时间总会拆穿一些,秘而不宣的事实,那就是没有人会一直在原地等你。
攒够失望的人,自然会放手,一颗心就像茶壶中开水,需要不断加火升温,才滚烫,你若对我认真,我就还你情深。
你若对我沉默,我就对你冷漠,我知道,我不是最好的那一个,却也是谁也代替不了的人,与人相遇一场。何其有辛,愿你
珍惜。
                                ——————   一禅心灵庙语

文章目录

  • Java多线程:创建多线程的"四种" 方式
    • 每博一文案
    • 一. 基本概念
      • 1.1. 程序
      • 1.2. 进程
      • 1.3. 线程
      • 1.4. 进程 与 线程 的关系
    • 二. 多线程的理解
      • 2.1. 单核CPU 和 多核 CPU的区别
      • 2.2. 并行 和 并发
      • 2.3. 同步编程 和 异步编程
      • 2.4. 多线程的好处
      • 2.5. 何时需要多线程
    • 三. 创建多线程的 "四" 种方式 及其 “启动” 多线程
      • 3.1.创建多线程方式一: extends Thread
        • 3.1.1 JDK 中的 Jconsole工具查看线程
        • 3.1.2 使用匿名子类,实现创建 Thread 子类的方式创建多线程
      • 3.2. 创建多线程方式二: implements Runnable
        • 3.2.1 使用匿名实现类,创建实现 Runnable 接口
      • 3.3 创建多线程方式三:implements Callable JDK5.0 新增的方式
      • 3.4. 线程池
        • 3.4.1 线程池相关APl 标准库中ThreadPoolExecuter构造方法()
        • 3.4.2 线程池的 ”执行流程“ 和 ”拒绝策略“
        • 3.4.3 线程池的优点
    • 四. 关于多线程创建的面试题:
      • 4.1 Thread 类 和 Runnable 创建多线程的异同点
      • 4.2 实现 Callable 接口的方式 与 实现 Runnable 接口的异同
    • 五. 总结:
    • 六. 最后:


一. 基本概念

1.1. 程序

程序(program) : 是为完成特定任务,用某种语言编写的一组指令的集合。即便是指 一段静态的代码,静态对象,运行在计算机内存当中。

1.2. 进程

进程(process) :具有执行独立的执行环境。一个进程通常有一套完整的,私有的基本运行时资源,特别地,每个进程都有自己内存空间,进程通常被视为程序或应用程序的代名词。然而,用户将其视为单个应用程序可能实际上时一组协作过程

进程简单的说就是一个类似:于运行 中的QQ,运行 中的网易音乐。是 程序 的一次执行过程,或是正在运行的一个程序。 是一个动态的过程:有它自身的产生,存在和消亡的过程。

程序是下载好的静态的,进程是运行的动态的

一个进程有占有一个JVM中的 方法区堆区空间

进程作为资源分配的基本单位 ,系统运行时会为每个进程分配不同的内存区域的空间,让其执行。

如我们:点击 ——> 电脑上的 ‘任务管理器’ 。就可以简单的查看,我们电脑上正在运行的 进程了。如下

在这里插入图片描述

1.3. 线程

线程(thread) : 有时被称为 ”轻量级进程“。进程和线程都提供了一个执行环境,但创建一个新的线程 创建一个新的线程要少得多的资源的消耗。

进程 进一步细化就是多个线程的组成,是一个程序 内部的一条执行的路径。所以简单的说:一个进程中可以包含多个线程,一个进程至少有一个线程存在。

若一个进程同一时间并行 执行多个线程,就是支持多线程的。

线程作为调度和执行的单位,每个线程拥有独立的运行程序计数器(pc),所以每个线程之间相互独立(压栈弹栈),互不干扰,多线程可以同时运行,互不影响,各自执行各自的。线程切换的开销小。

一个线程中多个线程共享相同的内存单元 / 内存地址空间。它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便,高效。 但多个线程操作共享的资源数据,可能存在 多线程的同步安全问题

一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程

在这里插入图片描述

1.4. 进程 与 线程 的关系

举一个例子:用来解释进程 与 线程的关系:

将阿里巴巴公司比作是一个进程:而马云是阿里巴巴公司中的一个线程,以及现在的 阿里 CTO 吴泽明也是一个线程,阿里巴巴所有的员工都是一个线程

将京东公司比作是一个进程:而其中的刘强东就是一个线程,京东中所有员工也是一个线程

在Java当中:一个进程:占用一个方法区 和 一个堆区,一个进程可以包含多个线程。而一个线程占用:一个栈区 和 一个程序计数器PC。

一个进程包含多个线程:同一个进程中的数据是被多个线程共享的,进程之间的数据是不共享的,就像 阿里巴巴公司的数据是不会和京东公司的数据共享的。一个线程一个栈:线程的数据是不共享的。启动10个线程,会有10个栈空间,每个栈和每个栈之间(每个线程和每个线程之间),互不干扰,各自执行各自的,这就是多线程并发。

如下是 JVM 对线程和进程关系的内存图:

在这里插入图片描述

堆(Heap) : 存放 new 对象的实例,几乎所有的对象的实例都在这里分配,

栈(Stack)(虚拟机栈) : 存放局部变量,方法执行完释放该变量所占空间(压栈弹栈)。

方法区(Method Area) : 存放 static 静态变量,静态方法,常量,类加载信息,静态代码块。

二. 多线程的理解

**背景:**计算机用户认为他们的系统一次可以做一件以上的事情。他们假设他们可以继续在文字处理器中工作, 而其他应用程序下载文件,管理打印队列和流音频。通常,一个单一的应用程序通常会一次做一件事情。 例如,流式音频应用程序必须同时从网络上读取数字音频,解压缩,管理播放和更新其显示。 即使文字处理器也应该随时准备好响应键盘和鼠标事件,无论重新格式化文本或更新显示器多么忙碌。 可以做这些事情的软件被称为 并发软件

一个线程就是一个执行流(按一定顺序执行的一组代码),每个线程之间都可以按照顺序执行自己的代码,多个线程之间 ”同时“执行多份代码,同一个线程中的这些线程,共用同一个进程中的(方法区 和 堆区)。

进程时资源分配的基本单位,线程是调度执行的基本单位

举例理解:

火车站的人工售票窗口:

三个人工售票窗口:窗口1,窗口2,窗口3

我去第一个窗口,你去地二个窗口,我买我的火车票,你买你的火车票,你不用等我,我不用等你。这样的售票的效率就提高了。

所以Java之所以有多线程机制:就是为了提高程序的处理业务的效率。

2.1. 单核CPU 和 多核 CPU的区别

首先我们先来思考一个问题:单核CPU 是可以 ”真正“ 的现实 多线程的吗 ???

答:是不可以的,但是可以模拟实现一种错误的 多线程 ,主要:利用的就是 CPU 的高频处理,以及时间片的轮转。

例如:现在有 两个线程: t1 线程 和 t2线程,需要单核CPU 处理执行,但是一个单核CPU 在同一个时间点是只能处理一件事的,

所以单核CPU 就这么做:一段时间处理t1线程的处理(但是t1这个线程的事务没有完成,可能就仅仅完成了 1/3 ),就停下来,去执行t2线程的事务了t2线程处理完了一段时间(t2线程的事务也没有处理完,可能处理完了 1/2),就跑去继续处理 t1线程没有处理完的事务了。

… 这些循环往复,直到所有t1线程,t2线程的事务都处理完了,单核CPU才休息。由于 单核CPU的太高频了(轮转处理线程太快了),让用户在使用上(一种单核CPU 也可以实现多线程的处理)的错觉。

就像我们 70/80 年代的 看的一个电影: 一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动态的。

而多核CPU 就是真正的多线程处理了,因为有多核CPU,所以可以同时间,让多个CPU 同时处理不同线程的事务。

2.2. 并行 和 并发

并行: 就是个CPU 同一时间,同时处理多个任务。比如多个人做同时做不同的事情

并发: 就是 CPU 采用时间片的方式,多个线程同时处理个同一件事情:比如 多个用户同时抢夺一张火车票,电商的秒杀。

在这里插入图片描述

2.3. 同步编程 和 异步编程

举例:处理两个线程的事务:分别是 t1线程,t2线程。

同步编程: CPU正在处理t1线程的事务,这时候的 t2线程不能被执行处理,只有等到t1线程处理完事务了,才能处理t2线程的事务,同步编程就是排队。

异步编程: CPU正在处理t1线程的事务,t2线程也可以被另外一个CPU处理,t2线程的处理不需要等待t1线程处理结束,t1线程也不需要等待t2线程的处理,各自处理各自的不需要等待对方。其实就是并发编程(提高效率)。

2.4. 多线程的好处

  1. 多线程并发编程:可以更充分利用 多核CPU 的多核的特点,提高效率。
  2. 其次是,多进程 虽然也可以实现并发编程,但是 线程进程 更轻量(无论是在 创建上,销毁上,调度上 线程都比进程更快)。
  3. 销毁线程,比销毁进程的代价更小,一个线程的数据的销毁,不会影响到其它的线程,线程之间是相互独立了,但是销毁进程 ,其中所有线程共享(方法区和堆区)中的数据可都没了。
  4. 提高应用程序的响应,用户的体验,如(程序加载资源:文本的加载比图片的加载更快,这就就可以用多线程处理,一个线程加载图片,一个线程加载文本,这样就不至于文本和图片都出不来了)。
  5. 改善程序结构,将既长又复杂的进程又分为多个线程,独立运行,利于理解和修改(如项目的模块化)。

2.5. 何时需要多线程

  1. 程序需要同时执行两个或多个任务(Java:mian主线程,gc垃圾回收线程,异常处理线程)。
  2. 程序需要实现等待的任务时,如:用户输入,文件读写,网络操作,搜索
  3. 需要一些后台的程序时。

三. 创建多线程的 “四” 种方式 及其 “启动” 多线程

在Java当中有 ”四“种方式创建多线程。

3.1.创建多线程方式一: extends Thread

在这里插入图片描述


Thread ( ) 的构成器

在这里插入图片描述

Thread();   // 创建一个新的线程对象。
Thread(String name);   // 创建一个新的线程对象,并设置其线程名
Thread(Runnable target);   // 创建新线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name);   // 创建一个新线程它实现了Runnable接口中的run方法,并设置线程名

创建多线程的步骤

  1. 创建 extends Thread 的子类。
class MyThread extends Thread {
}
  1. 重写Thread 中的 run()方法。
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

重写的如下:

@Override
    public void run() {
        for(int i = 0; i < 10;i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            // Thread.currentThread()  // 静态方法,方法当前线程的引用
            // Thread.currentThread().getName() // 返回当前线程名
        }
    }
  1. 创建 extends Thread 的子类的实例对象
// 方式一: 直接 new 对象
MyThread myThread = new MyThread();
// 方式二: 多态性: 动态绑定
Thread thread = new MyThread();  
  1. 调用 Thread 中的 start()方法:创建新线程,和启动调用run()的方法
myThread.start();

完成创建多线程实例:

package blogs.blog4;

public class ThreadTest1 {
    public static void main(String[] args) {
        // 3. 创建 extends Thread 的子类的实例对象
        MyThread myThread = new MyThread();
        Thread thread = new MyThread();   // 多态性: 动态绑定

        // 4. 调用Thread中的 start()方法,创建新线程,并启动run()方法
        myThread.start();

        // 主线程执行的任务。
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}


// 1. 创建继承 Thread 的类
class MyThread extends Thread {
    // 2. 重写 Thread 类中的 run()方法:
    // run()方法就是该分支线程要执行代码,处理的业务
    @Override
    public void run() {
        for(int i = 0; i < 10;i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            // Thread.currentThread()  // 静态方法,方法当前线程的引用
            // Thread.currentThread().getName() // 返回当前线程名
        }
    }

}

在这里插入图片描述

从运行结果上看,可以看到两个线程再 交替的执行 就说明我们是多线程创建成功了,并执行了。

为什么是线程会交替执行,有执行多的,也执行少的 ???

在这里插入图片描述

答:是因为多线程的执行,是需要抢夺CPU时间片(CPU的执行权),拿到了CPU的时间片才可以执行对应的线程的。

所以出现了线程的交替执行:一会主线程抢到了CPU时间片,下一秒被分支线程抢到了。

有多有少:是因为一个线程多次抢到了CPU的时间片,执行了线程中的程序。

具体的内容:大家可以移步至:🔜🔜🔜


3.1.1 JDK 中的 Jconsole工具查看线程

在我们安装的 JDK 中有一个名为 Jconsole 的工具,可以供我们查看在Java当中运行的多线程。

首先我们先编写一个创建多线程的 java程序:

创建两个线程:一个主main线程打印 100 以内的 偶数,另外一个分线程打印 100 0以内的 奇数。

需要注意的一点就是,如果想让 Jconsole 工具可以查看到我们运行的线程,则该线程需要进入休眠(阻塞)状态(可以使用Thread.sleep()方法让当前线程进入休眠(阻塞状态),该方法的具体信息大家可以移步至:🔜🔜🔜 ) 。不然,线程运行结束了,Jconsole 是无法查看到线程的执行的。

package blogs.blog4;

public class ThreadTest1 {
    public static void main(String[] args) {
        // 3. 创建 extends Thread 的子类的实例对象
        MyThread myThread = new MyThread();
        Thread thread = new MyThread();   // 多态性: 动态绑定

        // 4. 调用Thread中的 start()方法,创建新线程,并启动run()方法
        myThread.start();

        // main 主线程执行的任务。
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try { // 当前线程进入 休眠(阻塞)状态,让 Jconsole 可以查看到当前线程
                    Thread.sleep(1000 * 10);  // 当前main主线程休眠 10s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        }
    }
}


// 1. 创建继承 Thread 的类
class MyThread extends Thread {
    // 2. 重写 Thread 类中的 run()方法:
    // run()方法就是该分支线程要执行代码,处理的业务
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                try {   // 当前线程进入 休眠(阻塞)状态,让 Jconsole 可以查看到当前线程
                    Thread.sleep(1000* 10);   // 让当前线程 休眠 10s ,sleep()静态方法让当前线程休眠 s,单位毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->" + i);
                // Thread.currentThread()  // 静态方法,方法当前线程的引用
                // Thread.currentThread().getName() // 返回当前线程名
            }

        }
    }

}

在这里插入图片描述


  1. 打开 Jconsole 工具两种方式:方式一: 找到我们安装的 JDK 的目录,找到其中的 bin 目录。打开 bin 目录,从中找到一个名为 jconsole.exe 的可执行程序,双击打开就可以了。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


第二种打开:jconsole 的方式,使用 DOS 命令窗口,因为我们已经将 JDK 配置到 我们的电脑的环境变量当中去了,其中JDK的文件,我们可以直接通过 DOS命令的方式打开。如下:

首先 按住 win + R 键 ,输入 cmd 打开 DOS 命令窗口,输入 jconsole 就可以了。如下

在这里插入图片描述


  1. 运行我们创建多线程的程序,就是我们上面哪个打印 奇偶数的程序,再打开 Jconsole ,再本地进程中找到我们所运行的 main 方法的类名称,如下
    在这里插入图片描述

  2. 双击打开该进程,点击 不安全的连接,因为你也没有别的选择了。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意当,我们的线程运行结束了,该进程连接也就断了,就无法查看了


3.1.2 使用匿名子类,实现创建 Thread 子类的方式创建多线程

对于一些简单的多线程处理,我们可以使用 匿名子类的方式 继承 Thread 完成一个线程的任务。减少代码量。

对于上述:两个线程打印奇偶数的程序,改用 匿名子类的方式实现。如下:


public class ThreadTest1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){   // 多态性
            // 重写其中的乱方法
            @Override
            public void run() {
                // 打印 100 以内的 偶数
                for(int i = 1; i < 100; i++) {
                    if( i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + "--->" + i);
                    }
                }
            }
        };
        // 创建多线程,启动run()方法
        t1.start();

        Thread t2 = new Thread() {
            @Override
            // 打印奇数
            public void run() {
                for(int i = 1; i < 100; i++) {
                    if(i % 2 != 0) {   // Thread.currentThread().getName()返回当前线程名
                        System.out.println(Thread.currentThread().getName() + "-->" + i);
                    }
                }
            }
        };

        t2.start();
    }
}

在这里插入图片描述


多线程编程:

线程之间是相互独立的,各自运行各自的,各自执行各自的(压栈弹栈),互不影响,当一个线程停止了,其它线程仍然会继续执行

如下:我们在 main 主线程中定义一个 int num = 2 / 0 这个算数异常,让主线程中止,而分支线程继续。

package blogs.blog4;

public class ThreadTest1 {
    public static void main(String[] args) {
        Thread thread = new MyThread();  // 多态性

        // 分支线程的创建,启动run()方法
        thread.start();
        
        // main主线程,int num =  2/0; 算数异常,线程中止
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            int num = 2 / 0;  // 算数异常中止
        }

    }
}


// 1. 创建继承 Thread 的类
class MyThread extends Thread {
    // 2. 重写 Thread 类中的 run()方法:
    // run()方法就是该分支线程要执行代码,处理的业务
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "-->" + i);
                // Thread.currentThread()  // 静态方法,方法当前线程的引用
                // Thread.currentThread().getName() // 返回当前线程名
        }
    }

}

在这里插入图片描述

在这里插入图片描述


3.2. 创建多线程方式二: implements Runnable

介绍一下 Runnable 是一个接口

在这里插入图片描述


Runnable 接口的源码如下:



package java.lang;

/**
 * The <code>Runnable</code> interface should be implemented by any
 * class whose instances are intended to be executed by a thread. The
 * class must define a method of no arguments called <code>run</code>.
 * <p>
 * This interface is designed to provide a common protocol for objects that
 * wish to execute code while they are active. For example,
 * <code>Runnable</code> is implemented by class <code>Thread</code>.
 * Being active simply means that a thread has been started and has not
 * yet been stopped.
 * <p>
 * In addition, <code>Runnable</code> provides the means for a class to be
 * active while not subclassing <code>Thread</code>. A class that implements
 * <code>Runnable</code> can run without subclassing <code>Thread</code>
 * by instantiating a <code>Thread</code> instance and passing itself in
 * as the target.  In most cases, the <code>Runnable</code> interface should
 * be used if you are only planning to override the <code>run()</code>
 * method and no other <code>Thread</code> methods.
 * This is important because classes should not be subclassed
 * unless the programmer intends on modifying or enhancing the fundamental
 * behavior of the class.
 *
 * @author  Arthur van Hoff
 * @see     java.lang.Thread
 * @see     java.util.concurrent.Callable
 * @since   JDK1.0
 */
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

其中在 Runnable 接口当中只有一个 run() 的抽象方法

在这里插入图片描述


我们在回到 Thread 类当中,可以看到是 implements 实现了 Runnable 接口的,所以需要重写 接口 Runnable 中的 run()抽象方法的

public
class Thread implements Runnable {

Thread 重写的 run( ) 方法

private Runnable target;   // Thread 中定义的Runnable 类型


@Override
    public void run() {
        if (target != null) {
            target.run();   // 调用的其实就是 Runnable 接口中的 run()方法
        }
    }

还记得 Thread类 中有一个的构造器,拥有参数有 Runnable 的吗???,使用这个构造器创建线程。

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

从 Thread 类 和 Runnable 接口的源码分析,我们可以看出:Thread 中的 run()方法实际调用的就是 Runnable 接口中的 run()方法,所以方式一 extends Thread 类创建多线程时,重写的 run()方法其实重写的是 Runnable 接口中的 run()方法

在这里插入图片描述


这里我们使用 implements Runnable 接口 ,Thread(Runnable target) 的构造器创建新的 Thread 对象

步骤

  1. 创建 Runnable 接口的实现类
class MyRunnable implements Runnable {
  1. 重写 Runnable 接口中的 run() 抽象方法
@Override
public void run() {
    // 实现线程要处理的业务
    for(int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName() + "-->" + i);
        // Thread.currentThread().getName() 返回当前线程名
    }
}
  1. 创建实现 Runnable 接口的实现类的实例对象
MyRunnable myRunnable = new MyRunnable();
  1. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中,创建新的 Thread 对象
Thread thread = new Thread(myRunnable);

可以将第 3 步和 第 4 步结合起来

Thread thread2 = new Thread(new MyRunnable());
  1. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
thread.start();

完整创建多线程的方式

package blogs.blog4;

public class ThreadTest2 {
    public static void main(String[] args) {
        // 3. 创建 实现 Runnable 接口的实现类的实例对象
        MyRunnable myRunnable = new MyRunnable();
        // 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
        Thread thread = new Thread(myRunnable);
        // 或者 3,4步结合使用
        Thread thread2 = new Thread(new MyRunnable());

        // 5. 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
        thread.start();

        // main主线程
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            // Thread.currentThread().getName() 返回当前线程名
        }
    }
}


// 1. 创建 Runnable 的实现类
class MyRunnable implements Runnable {
    // 2. 重写 Runnable 中的 run()抽象方法
    @Override
    public void run() {
        // 实现线程要处理的业务
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            // Thread.currentThread().getName() 返回当前线程名
        }
    }
}

在这里插入图片描述


3.2.1 使用匿名实现类,创建实现 Runnable 接口

对于一些简单的线程处理,可以使用匿名实现类,调用 Thread(Runnable target) 创建新的Thread对象

两个线程:一个线程处理100以内的偶数,一个线程处理100以内的奇数。

package blogs.blog4;

public class ThreadTest2 {
    public static void main(String[] args) {
        // 创建匿名实现类 Runnable
        Thread t1 = new Thread(new Runnable() {
            @Override
            // 打印 100 以内的偶数
            public void run() {
                for(int i = 1; i <= 100 ; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                    // Thread.currentThread().getName() 返回当前线程的名称
                }
            }
        });


        // 创建匿名实现类 Runnable
        Thread t2 = new Thread(new Runnable() {
            @Override
            // 打印 100 以内的奇数
            public void run() {
                for(int i = 1; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                    // Thread.currentThread().getName() 返回当前线程的名称
                }
            }
        });


        // 调用Thread.start()方法创建新线程,启动重写Runnable 抽象方法的 run()方法
        t1.start();
        t2.start();

    }
}


// 1. 创建 Runnable 的实现类
class MyRunnable implements Runnable {
    // 2. 重写 Runnable 中的 run()抽象方法
    @Override
    public void run() {
        // 实现线程要处理的业务
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            // Thread.currentThread().getName() 返回当前线程名
        }
    }
}

在这里插入图片描述


3.3 创建多线程方式三:implements Callable JDK5.0 新增的方式

了解一下:Callable 接口

在这里插入图片描述

Callable 接口的源码



package java.util.concurrent;

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

从源码上分析,其实和 Runnable 是一样的,只有一个抽象方法,稍微不同的是 Runnable 接口中的run()抽象方法没有返回值,而这里Callable的 call()有返回值。

步骤:

  1. 创建 Callable 接口的实现类。
class MyCallable implements Callable {
}
  1. 重写 Callable 接口中的 call()重写方法,其 call()方法的作用和 Runnable 接口中的 run()方法的作用是一样的:线程处理的业务,不同的一点就是,call()方法有返回值,run()方法没有返回值。
@Override
public Object call() {
    int sum = 0;
    for(int i = 0; i <= 100; i++) {
        sum += i;
        System.out.println(Thread.currentThread().getName() + "--->" + i);
        // Thread.currentThread().getName()返回当前线程名
    }

    return sum; // 返回值: 自动装箱
}
  1. 创建 Callable 实现类的实例对象
MyCallable myCallable = new MyCallable();
  1. 将此Callable的实例对象作为传递到 FutureTask 构造器中,创建 FutureTask 的实例对象
FutureTask futureTask = new FutureTask(myCallable);

FutureTask 的介绍:

FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

在这里插入图片描述

在这里插入图片描述


其实我们要使用的是 Future 这个接口,只是接口不能 new ,所以就只能使用 实现了 Future 这个接口的类了 ——> FutureTask 类了,

在这里插入图片描述


调用 FutureTask(Callable<v> callable)构造器 创建 FutureTask 的对象

FutureTask futureTask = new FutureTask(myCallable);
  1. 将 FutureTask 对象作为参数传递到 Thread(Runnable target) 构造器中创建 Thread对象
Thread thread = new Thread(futureTask);

Thread(Runnable target) 构造器,其中的 FutureTask 因为实现了 Runnable 接口所以可以作为 Runnable 类型的参数使用多态。

public class FutureTask<V> implements RunnableFuture<V> {
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以将 第 3 步,第 4 步和第 5步结合起来

Thread thread2 = new Thread(new FutureTask(new MyCallable()));
  1. 调用其中的Thread对象中的 start()方法:创建新线程,启动其中的call()方法
thread.start();
  1. 通过 FutureTask中的 get()对象方法, 获取到 Callable 中的 calll() 方法的 返回值 。当然这一步,也可以省略不写,我不想要获取 call()的返回值。
try {
    Object object = futureTask.get(); // 获取到 call()方法的返回值
    System.out.println("总和: " + object); // 自动拆箱
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

在这里插入图片描述


完整的实现流程如下:

package blogs.blog4;


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建多线程的方式三: implements Callable
 */
public class ThreadTest3 {
    public static void main(String[] args) {
        // 3. 创建Callable 接口的实现类的实例对象
        MyCallable myCallable = new MyCallable();

        // 4.将此Callable的实例对象作为传递到 FutureTask构造器中,创建FutureTask的实例对象
        FutureTask futureTask = new FutureTask(myCallable);

        // 5. 将 FutureTask 对象作为参数传递到 Thread(Runnable target) 构造器中创建 Thread对象
        Thread thread = new Thread(futureTask);

        // 可以将 第 3 步,第 4 步和第 5步结合起来
        Thread thread2 = new Thread(new FutureTask(new MyCallable()));

        // 6. 调用其中的Thread对象中的 start()方法:创建新线程,启动其中的call()方法
        thread.start();

        // main()主线程
        for(int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }

        // 7. 通过FutureTask 中的 get() 对象方法获取到 call()方法的返回值
        try {
            Object object = futureTask.get(); // 获取到 call()方法的返回值
            System.out.println("总和: " + object); // 自动拆箱
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }
}


// 1. 创建 Callable 的实现类
class MyCallable implements Callable {
    // 2. 重写 Callable 的抽象方法 call() 和 Runnable 中的 run()方法是一样的作用,不同的是 call()有返回值处理
    @Override
    public Object call() {
        int sum = 0;
        for(int i = 0; i <= 100; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            // Thread.currentThread().getName()返回当前线程名
        }

        return sum; // 返回值: 自动装箱
    }
}

在这里插入图片描述


3.4. 线程池

了解

大多数执行器实现 java.util.concurrent 使用的线程池,由工作线程组成。这种线程从单独存在 RunnableCallable 它执行,通常用于执行多个任务的任务。

使用工作线程可以最大限度地减少由于线程创建而导致的开销。线程对象使用大量内存,而在大规模应用程序中,分配和释放许多线程对象会创建显着的内存管理开销。

一种常见类型的线程池是固定线程池。这种类型的池总是具有指定数量的线程运行; 如果一个线程在某种程度上仍然在使用中被终止,它将被一个新的线程自动替换。任务通过内部队列提交到池中,每当有比线程更多的活动任务时,它将保存额外的任务。

固定线程池的一个重要优点是使用它的应用程序正常地降级。要了解这一点,请考虑一个 Web 服务器应用程序,其中每个 HTTP 请求由单独的线程处理。如果应用程序只是为每个新的 HTTP 请求创建一个新的线程,并且系统接收到的请求超过它可以立即处理的请求,那么当所有这些线程的开销超过系统的容量时,应用程序将突然停止对所有请求的响应。对于可以创建的线程数量的限制,应用程序将不会尽快为 HTTP 请求提供服务,但是它将尽可能快地对系统进行维护。

背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路: 提前创建好多个线程,放入线程池当中,需要使用线程时,直接去线程池中获取,使用完线程后,放回线程池底当中去,可以避免频繁创建销毁,实现重复利用。

步骤:

  1. 创建一个可重用的线程池,使用 Executors.newFixedThreadPool()静态方法返回一个线程池,用 ExecutorService 接口接收。
ExecutorService executorService = Executors.newFixedThreadPool(10);

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


  1. 创建一个实现 Runnable 接口的实现类 / Callable 接口的实现类
class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}
// 2. 创建一个实现 Callable 接口的实现类
class MyCallable2 implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for(int i = 0; i < 10; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
        return sum;
    }
}
  1. 将创建的线程类Runnable/Callable实现类的对象交给线程池,让线程池中的线程来复杂执行

需要注意的是: 对于 Runnable 接口的线程任务使用 executorService.execute()的对象方法,该方法没有返回值而对于 Callable接口的线程任务使用 executorService.submit()的对象方法,可以接受Callable 中的 call()的返回值。

在这里插入图片描述

在这里插入图片描述

executorService.execute(new MyRunnable2());   // 用于 Runnable 接口类型的
Object object = executorService.submit(new MyCallable2());   // 用于 Callable 接口类型的有返回值
  1. 使用完了线程池,需要关闭线程池 使用 executorService.shutdown()对象方法关闭线程池。

在这里插入图片描述

executorService.shutdown();

完整的实现方式如下:

package blogs.blog4;


import java.util.concurrent.*;

/**
 * 创建多线程的第四种方式:线程池
 */
public class ThreadTest4 {
    public static void main(String[] args) {
        // 1. 创建一个可重用固定的线程数的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        // 3. 将创建的线程类Runnable/Callable实现类的对象交给线程池,让线程池中的线程来复杂执行
        executorService.execute(new MyRunnable2());   // 用于 Runnable 接口类型的
        Future future = executorService.submit(new MyCallable2());// 用于 Callable 接口类型的有返回值
        try {
            System.out.println("总值: " + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 4. 使用完线程池,关闭线程池
        executorService.shutdown();

    }
}


// 2. 创建一个实现 Runnable 接口的实现类 / Callable 接口的实现类
class MyRunnable2 implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}


// 2. 创建一个实现 Callable 接口的实现类
class MyCallable2 implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for(int i = 0; i < 10; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
        return sum;
    }
}

在这里插入图片描述


3.4.1 线程池相关APl 标准库中ThreadPoolExecuter构造方法()

支持很多参数,支持很多选项,可以创建出不同风格的线程池

img

/**
* 设置线程池的属性值
* @param args
*/
public static void main(String[] args) {

    // 1. 创建可重用固定的数量的线程的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(10); // 10个可重用的线程的线程池

    // 2.创建设置线程池的对象
    ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
    /*
        如下 ThreadPoolExecutor 的继承关系
        public class ThreadPoolExecutor extends AbstractExecutorService
        public abstract class AbstractExecutorService implements ExecutorService
        如下 ExecutorService 的继承关系
        public interface ExecutorService extends Executor
         */

    // 设置线程池的核心池的大小
    threadPoolExecutor.setCorePoolSize(15);
    // 设置线程池的最大线程数
    threadPoolExecutor.setMaximumPoolSize(20);
    // 设置线程在终止之前可能保持空闲的时间限制。
    // 如果存在超过当前在池中的线程核心数量,则在等待这段时间而不处理任务之后,多余的线程将被终止。 这将覆盖在构造函数中设置的任何值。
    threadPoolExecutor.setKeepAliveTime(1000,null);

}

3.4.2 线程池的 ”执行流程“ 和 ”拒绝策略“

线程池的执行流程:

  1. 先判断当前线程数是否大于核心线程数,如果结果为 不大于 true,则新建线程并执行任务。
  2. 如果结果为 true ,则判断任务队列是否已满,如果结果为 已经满 false,就把任务添加到任务队列中等待线程执行。
  3. 如果结果为true,则判断当前线程数量是否超过最大线程数,如果结果为没有超过 false ,则新建线程执行此任务。
  4. 如果结果为 false,则执行拒绝策略。

拒绝策略 :

AbortPoliy : 中止策略,线程池会抛出异常并中止执行此次任务。

CallerRunPolicy:把任务交给此次任务 main 线程来执行。

DiscardPolicy:忽略新任务,先执行旧任务。

DiscardOldestPolicy:忽略最早的任务,执行新加入的任务。

3.4.3 线程池的优点

  1. 降低资源的消耗:通过重复利用线程池中的线程,不需要每次每次都创建:当需要执行任务的时候,不需要创建线程了,而是直接从线程池中取一个现成的线程直接使用,用完以后也不用销毁线程,而是归还给线程池。(如果没有线程池的话,创建一个线程就需要有额外的销毁线程的开销,如果频繁的创建线程,紧跟着的是线程销毁的开销也就不能忽略了)。
  2. 提高响应数量:(省去了创建线程这一步,所以当拿到任务时,就可以立即执行了)。
  3. 便于管理线程:我们可以对线程池进程一些设置的属性:比如定时,延时来执行某些线程,也可以控制最大并发线程数等功能

四. 关于多线程创建的面试题:

4.1 Thread 类 和 Runnable 创建多线程的异同点

  1. Thread 是类,而Runnable 是接口。
  2. Thread 本身就实现了 Runnable 接口。
public class Thread implements Runnable {
}
  1. 某某线程 implements 实现了某个接口的功能 比 某某线程继承了 Thread 类,更符合寓意,生活实际。

  2. 由于Java是单继承的关系:如果是 extends Thread 类的方式创建线程,就无法再继承其他的类了,存在局限性,而 Runnable 接口可以实现多个接口,因此 Runnable 的方式 扩展性更好。

  3. Runnable 还可以用于 “资源的共享”,即,多线程都是基于 某一个 Runnable 对象建立的,它们会共享 Runnable 实例对象上的资源。通常使用的都是 Runnable 的方式创建多线程

4.2 实现 Callable 接口的方式 与 实现 Runnable 接口的异同

  1. 实现 Callable 接口 与 实现 Runnable 接口创建多线程的方式基本上时一样的。
  2. 不同的是 Callable 接口中的 call()方法有返回值,可以抛异常 Exception (编译异常)所以可以自定义异常,支持泛型的返回,而Runnable 接口中的 run() 方法没有返回值,不可以抛出异常,更不支持泛型的返回。
  3. Callable 线程执行时,需要等待 IO 的读取的返回值,这里线程会处于阻塞状态,等待读取到返回值,才会恢复到 就绪状态,效率上没有 Runnable 快,因为 Runnable 不用处理这些。

五. 总结:

  1. 一个进程占用一个方法区(类的加载,静态方法/变量,常量,静态代码块) 和 一个堆区(类对象)。一个进程包含多个进程,一个进程至少一个线程。进程与进程之间是相互独立的。
  2. 一个线程占用一个栈和一个程序计数器PC,同一个进程中的方法区和堆区中的数据是所有线程共享的。线程与线程之间相互独立,各自执行各自的(压栈弹栈),互不影响。比如:main线程停止了,分支线程并不会停止而是继续执行。
  3. 并行:多个线程处理同时处理多个任务,并发:多个线程同时处理一个任务:火车抢票,电商秒杀。
  4. 单核CPU 的 “模拟”多线程处理的时间片原理
  5. 多线程的好处。
  6. 创建多线程的 “四种方式”: 1. extends Thread, 2. implement Runnable 接口 ,3. implement Callable 接口,4. 创建线程池获取到线程池中的线程。
  7. JDK 中的 Jconsole 工具查看线程的使用。
  8. extends Thread 中的匿名继承子类的,implement Runnable 接口中的匿名实现类
// extends Thread 中的匿名继承子类的
Thread t1 = new Thread(){   // 多态性
            // 重写其中的乱方法
            @Override
            public void run() {
                // 打印 100 以内的 偶数
                for(int i = 1; i < 100; i++) {
                    if( i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + "--->" + i);
                    }
                }
            }
        };
      // 创建匿名实现类 Runnable
        Thread t1 = new Thread(new Runnable() {
            @Override
            // 打印 100 以内的偶数
            public void run() {
                for(int i = 1; i <= 100 ; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                    // Thread.currentThread().getName() 返回当前线程的名称
                }
            }
        });
  1. Thread 与 Runnable 的异同。

  2. Runnable 与 Callable 的异同。

  3. 线程池的好处。

六. 最后:

限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见 !!!

在这里插入图片描述

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

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

相关文章

buctoj-2023寒假集训-进阶训练赛(八)

问题 A: 分离出整数n从右边数第k个数字&#xff0c;递归实现 题目描述 在程序中定义一函数digit(n,k)&#xff0c;它能分离出整数n从右边数第k个数字。 输入 正整数n和k。 输出 第k个数字(若不存在则输出0&#xff09; 样例输入 31859 3 样例输出 8 #include<bits/stdc.h&g…

电商直播小程序核心功能有哪些?电商直播小程序代码分析

一个优质的电商直播小程序&#xff0c;必须带有后台管理&#xff0c;模块功能分工明确&#xff0c;可以让商家及时管理商品。在管理后台端又分为会员、商品、订单、店铺、直播、分销、优惠券、物流、数据等功能列表栏&#xff0c;基本功能较完善。下文小编将为大家讲解一下电商…

Linux命令行中 git 的使用

文章目录&#xff1a;什么是gitgitee新建仓库git提交代码1.同步远程仓库代码 - git pull2.查看本地仓库的状态 - git status3.添加代码到本地.git缓冲区 - git add4.推送代码到本地仓库.git中 - git commit5.同步本地仓库.git的内容到远程仓库 - git push什么是git Git 是一个…

2023年了,浏览器竟然还有新玩法,能看热搜能领券

在移动互联网时代&#xff0c;手机浏览器是手机中不可缺少的APP之一。我们经常使用手机浏览器查资料&#xff0c;看新闻&#xff0c;看小说等等。如今&#xff0c;手机浏览器的功能越来越强大&#xff0c;玩法也越来越多。最近&#xff0c;发现一款手机浏览器&#xff0c;竟然聚…

立创EDA入门3 通过51单片机最小板学习PCB设计

立创EDA入门3 通过51单片机最小板学习PCB设计一、本文目的二、原理图设计1. 新建工程&#xff0c;命名为51系统2. 各模块原理图3. 一些常用操作&#xff08;1&#xff09;放置普通元器件&#xff08;2&#xff09;封装、标签设置&#xff08;3&#xff09;在线库中查找元器件&a…

硬件系统工程师宝典(4)-----传输过程的信号要如何描述?

各位同学大家好&#xff0c;欢迎继续做客电子工程学习圈&#xff0c;今天我们继续来讲这本书&#xff0c;硬件系统工程师宝典。上篇我们说到为实现信号的有效传输&#xff0c;需要保证信号波形的完整和信号时序的完整&#xff0c;并且知道了从时域、频域两个角度去分析信号。那…

高压功率放大器在诱发肌电运动阈值对比研究中的应用

实验名称&#xff1a;经颅磁声刺激与经颅超声刺激诱发肌电运动阈值的对比研究 研究方向&#xff1a;生物医学 测试目的&#xff1a; 在脑科学与神经科学研究中&#xff0c;物理刺激是目前应用最广泛的电磁刺激技术。该技术利用变化的磁场诱发感应电流进而对神经组织进行调控&am…

递归算法实例应用(一)

递归算法实例应用&#xff08;一&#xff09; 递归简笔 递归和普通函数调用一样&#xff0c;都是通过函数栈实现。 以斐波那契数列递归调用为例 递归时函数调用栈的进栈、出栈过程可以由上述图示直观的体现出来&#xff0c; 因此可以得出递归的几个作用&#xff1a; ​ …

玩转CodeQLpy之代码审计实战案例

0x01 背景介绍CodeQLpy是一款半自动化的代码审计工具&#xff0c;能有效提高代码审计的效率&#xff0c;目前项目仍处于测试阶段。项目地址https://github.com/webraybtl/CodeQLpy&#xff0c;在github主页有对应的安装和使用介绍&#xff0c;如图1.1所示。-t: 指定待扫描的源码…

SignalR在Asp.NetCore的使用入门

SignalR在ASP.NET CORE下的使用 SignalR是微软提供的实时Web通信的库&#xff0c; 它会自己根据情况使用不同的链接方式&#xff0c;主要有 WebSocketlong pollingForever frameSever Events Sent 等方式&#xff0c;故可以不管其具体的实现方式&#xff0c;差不多等于开箱…

Docker部署 Oracle12c

Docker部署 Oracle12c Docker部署 registry Docker搭建 svn Docker部署 Harbor Docker 部署SQL Server 2017 Docker 安装 MS SqlServer Docker部署 Oracle12c 文章目录Docker部署 Oracle12c部署步骤1.查询oracle镜像2.下载镜像3.创建容器4.进入容器5.连接oracle数据库部署步骤…

Unreal动态多播委托

相对于普通多播委托,动态多播委托可以在蓝图中绑定事件。在这里记录一下它的使用方式:定义动态多播委托类型:声明委托变量:绑定函数:通过Add绑定:需要定义FScriptDelegate变量:为FScriptDelegate添加绑定函数这种方法的好处是可以针对单个函数进行解绑:通过AddDynamic标记宏绑定…

Linux 调度器之抢占式调度

文章目录前言一、简介1.1 need_resched标志1.2 try_to_wake_up二、抢占调度2.1 抢占简介2.2 用户态抢占2.2.1 从系统调用返回用户空间2.2.2 从中断返回用户空间2.3 内核态抢占2.2.1 preempt_count2.2.2 调用preempt_enable()2.2.3 从中断返回内核空间时总结参考资料前言 在这片…

worker多线程的使用与理解

JavaScript是单线程的&#xff0c;并不是说它是单线程语言&#xff0c;只能说在浏览器中运行是单线程的&#xff0c;单线程会免去许多麻烦&#xff0c;比如说&#xff0c;有两个线程同时进行DOM操作&#xff0c;一个是在父级下添加子元素&#xff0c;一个是删除这个父级元素&am…

Pandas数据清洗函数大全

文章目录 一、drop()&#xff1a;删除指定行列 1. 删除指定行2. 删除指定列 二、del()&#xff1a;删除指定列三、isnull()&#xff1a;判断是否为缺失 1. 判断是否为缺失2. 判断哪些列存在缺失3. 统计缺失个数 四、notnull()&#xff1a;判断是否不为缺失五、dropna()&#x…

Java笔记024-Math类、Arrays类、System类、BigInteger和BigDecimal类、日期类

常用类Math类基本介绍Math类包含用于执行基本数学运算的方法&#xff0c;如初等指数、对数、平方根和三角函数方法一览(均为静态方法)Math常见方法应用案例1、 abs绝对值2、pow求幂3、ceil向上取整4、floor 向下取整5、round 四舍五入6、sqrt 求开方7、random求随机数//思考&am…

Webpack常见的插件和模式

1、认识Plugin Webpack的另一个核心是Plugin&#xff0c;官方有这样一段对Plugin的描述&#xff1a; While loaders are used to transform certain types of modules, plugins can be leveraged to perform a wider range of tasks like bundle optimization, asset manageme…

《对线面试官》| 高频计算机网络面试题 pt.2

目录11、Get与POST的区别12、Session、Cookie 的区别13、简单聊聊 HTTP 协议吧14、URI 和 URL 的区别15、GET 和 POST 方法都是安全和幂等的吗&#xff1f;16、说说 HTTP/1.1 相比 HTTP/1.0 提高了什么性能&#xff1f;17、那上面的 HTTP/1.1 的性能瓶颈&#xff0c;HTTP/2 做了…

中国蚁剑的工作原理

中国蚁剑连接http://192.168.11.157/dvwa/hackable/uploads/pass.php蚁剑连接并同时用wireshark抓取流量1274 行&#xff0c;追踪tcp流因为我们的php.php内容是 $_POST[pass]&#xff0c;所以这里是post了一个pass参数&#xff0c;后面跟上了命令。通过站长工具-URL解码/编码 (…

15.Isaac教程--Isaac机器人引擎简介

Isaac机器人引擎简介 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录Isaac机器人引擎简介基础Codelets完整的应用基础 本节介绍如何使用 Isaac 机器人引擎。 它介绍了相关术语并解释了 Isaac 应用程序的结构。 Isaac 应用程序由 JavaS…