Java——线程池详细讲解

文章目录

  • 一、线程池
  • 一、线程池基础
    • 1.1 什么是线程池
    • 1.2 为什么使用线程池
    • 1.3 线程池有哪些优势
    • 1.4 应用场景
  • 二、线程池使用
    • 2.1 Java内置线程池 ThreadPoolExecutor
      • 2.1.1 线程池的七个参数
        • 2.1.1.1 **int corePoolSize 核心线程数量**
        • 2.1.1.2 int maximumPoolSize 最大线程数
        • 2.1.1.3 long keepAliveTime 最大空闲时间
        • 2.1.1.4 TimeUnit unit 时间单位
        • 2.1.1.5 BlockingQueue<Runnable> workQueue 任务队列
        • 2.1.1.6 ThreadFactory threadFactory 线程工厂
        • 2.1.1.7 RejectedExecutionHandler handler 饱和处理机制
    • 2.2 线程池工作流程介绍
  • 三、自定义线线程池
    • 3.1 参数设计分析
      • 3.1.1 核心线程数量 corePoolSize
      • 3.1.2 任务队列长度 workQueue
      • 3.1.3 最大线程数 maximumPoolSize
      • 3.1.4 最大空闲时间 KeepAliveTime
    • 3.2 自定义线程池实现步骤
      • 3.2.1 编写任务类(MyTask) 实现Runnable接口
      • 3.2.2 编写线程类(MyWork) 用于执行任务 需要持有所有任务
      • 3.2.3 编写线程池类(MyThreadPool) 包含提交任务,执行任务能力
      • 3.2.4 编写测试类 创建线程池对象,提交多个任务测试
  • 四、 Java内置线程池 - ExecutorService 介绍
    • 4.1 常用方法
    • 4.2 ExecutorService 获取
      • 4.2.1 static ExecutorService newCachedThreadPool ( )
      • 4.2.2 static ExecutorService newCachedThreadPool (ThreadFactory threadFactory)
      • 4.2.3 static ExecutorService newFixedThreadPool(int nThreads)
      • 4.2.4 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
      • 4.2.5 static ExecutorService newSingleThreadExecutor()
      • 4.2.6 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
  • 五、Java内置线程池 - ScheduledExecutorService
    • 5.1 ScheduledExecutorService 获取
      • 5.1.1 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
      • 5.1.2 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
      • 5.1.3 static ScheduledExecutorService newSingleThreadScheduledExecutor()
      • 5.1.4 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
    • 5.2 ScheduledExecutorService 常用方法
      • 5.2.1 <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
      • 5.2.2 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
      • 5.2.3 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
      • 5.2.4 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
  • 六、 Future - 异步计算结果
    • 6.1 常用方法
    • 6.2 Future演示
  • 七、 综合案例
    • 7.1 案例
    • 7.2 代码

一、线程池

​ 线程池是一种常见的多线程编程技术,它可以在执行任务时复用已创建的多个线程,并且可以控制同时运行的线程数以避免资源占用过多的问题。下面是一个简单的Java示例代码,演示如何使用线程池。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个容量为 5 的线程池
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Task(i)); // 提交任务,由线程池中空闲的线程执行
        }
        executorService.shutdown(); // 调用任务完成,关闭线程池
    }

    static class Task implements Runnable {
        private int taskNum;

        public Task(int num) {
            this.taskNum = num;
        }

        @Override
        public void run() { // 线程池中的线程会调用该方法进行具体任务的执行
            System.out.println("正在执行task " + taskNum);
            try {
                Thread.sleep(1000); // 模拟任务耗时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("task " + taskNum + " 执行完毕");
        }
    }
}

​ 上述代码定义了一个包含10个任务的线程池(容量为5),每个任务被分配到池中可用的空闲线程,当线程完成任务后,它会返回线程池以便其他任务使用。当所有任务完成时,调用executorService.shutdown()方法关闭线程池。

​ 总之,在编写多线程应用程序时,利用线程池可以更有效地管理资源和提高系统性能。同时,由于在线程池中的线程具有重复使用性、复用性和恰当的个数等优点,因此建议在需要频繁创建并执行线程的情况下,尽可能使用线程池来实现。

一、线程池基础

1.1 什么是线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。

​ 这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象

1.2 为什么使用线程池

​ 使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处;

1.3 线程池有哪些优势

  • 线程和任务分离,提升线程重用性
  • 控制线程并发数量,降低服务器压力,统一管理所有线程
  • 提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么线程池就免去了T1和T3的时间

1.4 应用场景

应用场景介绍:

  • 网购商品秒杀
  • 云盘文件上传和下载
  • 12306网上购票系统

只要有并发的地方、任何数量大或小、每个任务执行时间长活短都可以使用线程池

只不过在使用线程池的时候,注意一下设置合理的线程池大小即可

二、线程池使用

2.1 Java内置线程池 ThreadPoolExecutor

2.1.1 线程池的七个参数

2.1.1.1 int corePoolSize 核心线程数量

线程池中的核心线程数量指的是线程池在正常情况下需要保持的最小线程数量,当我们执行任务时线程线程数量没有达到核心线程数量,那就会新开线程。

​ 当有任务提交到线程池时,线程池会先尝试通过核心线程来处理任务。只有当核心线程都被占用,并且任务队列已满时,线程池才会创建新的线程。因此,核心线程数量的大小可以直接影响线程池的性能和资源消耗。如果核心线程数量设置过大,可能会浪费系统资源;如果设置过小,可能会导致任务等待时间增加,进而降低系统的响应速度。在实际应用中,应根据具体的业务场景和系统负载情况来合理设置线程池的核心线程数量。

2.1.1.2 int maximumPoolSize 最大线程数

线程池中的最大线程数量指的是线程池允许创建的最大线程数,包括核心线程和非核心线程。

当任务队列已满,并且当前线程数小于最大线程数时,线程池会创建新的线程来处理任务。但是设置过大的最大线程数可能会带来一些负面影响,如增加系统资源消耗和降低系统的稳定性。因此,在合理使用线程池进行任务调度的前提下,需要根据业务场景和系统负载情况合理设置线程池的最大线程数量。

2.1.1.3 long keepAliveTime 最大空闲时间

​ 也可以叫做存活时间。

​ 当我们的一个线程不用的时候允许空闲,但是空闲到一定时间之后线程池也会回收线程。

​ 线程池中的最大空闲时间指的是一个非核心线程在空闲状态下保持存活的最长时间。

​ 当线程池中的非核心线程数量超过了核心线程数量时,超过核心线程数量的这些线程被称为“非核心线程”。

​ 这些非核心线程在处理完任务后并不会立即销毁,而是处于等待下一次任务的空闲状态。线程池中的最大空闲时间设置了一个阈值,若一个非核心线程在空闲状态下超过了该时间限制,线程池就会将其销毁,以释放系统资源。

合理设置线程池的最大空闲时间可以有效避免因长时间运行的线程造成的资源浪费和系统负载过高的问题。但是如果将最大空闲时间设置得太短,则可能导致过多的线程被销毁和创建,反而会影响性能。正确地设置线程池的参数需要综合考虑业务场景和系统负载情况,并通过实验和观测对其进行调优

2.1.1.4 TimeUnit unit 时间单位

枚举类

image-20230426113405452

2.1.1.5 BlockingQueue workQueue 任务队列

作用:暂存尚未执行的任务的数据结构(临时缓冲区)

当我们的线程数量达到核心线程数量后,如果再有任务提交到线程池里面,此时不会里面创建新线程,而是将任务添加到任务队列里面去,只有当任务队列加满后,按照我们设置的最大线程数来逐步创建线程(创建的线程一定不大于最大线程数)

2.1.1.6 ThreadFactory threadFactory 线程工厂

允许我们自己参与创建线程的过程

 class SimpleThreadFactory implements ThreadFactory { 
     public Thread newThread(Runnable r) { 
         return new Thread(r);
     } 
 } 

2.1.1.7 RejectedExecutionHandler handler 饱和处理机制

​ 线程数到达核心线程数量,并且任务队列满了,线程数也到达最大线程数了, 也就是说线程池处于饱和状态了,已经无法再融入其他任务了,那这个时候我们就能给出一个饱和处理机制。

​ 比如让那些任务等一会啊,抛弃一些任务啊

  • 创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂和拒绝执行处理程序。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)

  • 创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)

  • 创建一个新的 ThreadPoolExecutor与给定的初始参数和默认拒绝执行处理程序。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)

  • 创建一个新 ThreadPoolExecutor给定的初始参数。

    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

2.2 线程池工作流程介绍

image-20230426114734896

image-20230426115721098

三、自定义线线程池

3.1 参数设计分析

3.1.1 核心线程数量 corePoolSize

​ 核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定

​ 例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理

3.1.2 任务队列长度 workQueue

任务队列长度一般设计为:核心线程数/单个任务执行时间成×2即可

例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200

3.1.3 最大线程数 maximumPoolSize

最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定

例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,

最大线程数=(最大任务数-任务队列长度)×单个任务执行时间 : 最大线程数=(1000-200)×0.1=80

3.1.4 最大空闲时间 KeepAliveTime

这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值。

用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可。

3.2 自定义线程池实现步骤

3.2.1 编写任务类(MyTask) 实现Runnable接口

/**
 * 要求:
 *    自定义线程池练习,这是任务类,需要实现接口
 *    包含任务编号,每一个任务执行时间设计为0.2秒
 */
public class MyTask implements Runnable {

    private int id ; //  任务编号

//   由于run方法是重写接口中的方法,不可添加形式参数,但是我们可以通过构造方法添加

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println("线程:"+name+"即将执行任务:"+id);
        try {
            Thread.sleep(200);  //休眠200毫秒
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程:"+name+"完成了任务:"+id);
    }
    
    public MyTask(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "MyTask{" +
                "id=" + id +
                '}';
    }
}

3.2.2 编写线程类(MyWork) 用于执行任务 需要持有所有任务

所有的任务用一个集合存起来


/**
 *  需求:
 *     编写一个线程类,需要去继承Thread,设计一个属性,用于保存线程的名字
 *     设计一个集合,用于保存所有的任务
 */
public class MyWorker extends Thread{

    private String name;  //保存线程名字
    private List<Runnable> tasks;  //保存将来所有的任务

    /**
     *  判断集合中是否有任务,只要有任务,就一直执行
     */
    @Override
    public void run() {
        while (tasks.size()>0){
//          将第一个任务移除出来
            Runnable runnable = tasks.remove(0);
//          一直执行任务
            runnable.run();
        }
    }

//  利用构造方法给成员变量赋值
    public MyWorker(String name, List<Runnable> tasks) {
        super(name);
//        this.name = name;
        this.tasks = tasks;
    }
}

3.2.3 编写线程池类(MyThreadPool) 包含提交任务,执行任务能力

/**
 *  自定义的线程池类
 *  成员变量:
 *    1. 任务队列    集合来表示即可(线程安全的集合)
 *    2. 当前线程数量
 *    3. 核心线程数量
 *    4. 最大线程数
 *    5. 任务队列长度
 *   没有设置最大空闲时间
 *
 *  成员方法:
 *    1.提交任务:将任务添加到集合中(如果没有超出任务队列的长度则可以加入到任务队列中)
 *    2.执行任务: 判断当前线程的数量,决定创建核心线程(如果当前线程数量在核心线程数量之下)还是非核心线程(当前线程数量在核心线程数量与最大线程数量之间)
 */
public class MyThreadPool {
//    1. 任务队列  集合来表示即可(线程安全的集合)
//         LinkedList集合是非线程安全的,我们可以使用集合工具类中方法将其转变为线程安全的
    private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());

//     2. 当前线程数量,初始时是0
    private int num;

//     3. 核心线程数量
    private int corePoolSize;

//     4. 最大线程数
    private int maxSize;

//    5. 任务队列长度
    private int workSize;

//    1.提交任务:
    public void submit(Runnable runnable){
//       判断当前集合中任务的数量,是否超出了最大任务数量
        if (tasks.size()>=workSize){
//          TODO  简陋的饱和机制处理
            System.out.println("任务:"+runnable+"被丢弃了");
        }else {
            tasks.add(runnable);
//          TODO 执行任务
            execTask(runnable);  //这个地方最好传入线程,不传入任务
        }
    }


//   2.执行任务:
    private void execTask(Runnable runnable) {
//      TODO 判断当前线程池中的线程总数量,是否超出了核心数
        if(num <=corePoolSize){
            MyWorker myWorker = new MyWorker("核心线程:"+num,tasks);
//            Thread t = new Thread(myWorker);
//          启动线程
            myWorker.start();
            num++;
        }else if (num < maxSize){
            MyWorker myWorker = new MyWorker("非核心线程:"+num,tasks);
//            Thread t = new Thread(myWorker);
//          启动线程
            myWorker.start();
            num++;
        }else {
//            因为我们在execTask之前,已经将任务添加到tasks集合中了,所以说是被缓存了
            System.out.println("任务:"+runnable+"被缓存了");
        }
    }

    public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
        this.corePoolSize = corePoolSize;
        this.maxSize = maxSize;
        this.workSize = workSize;
    }
}

3.2.4 编写测试类 创建线程池对象,提交多个任务测试

/**
 * 1.创建线程池对象
 * 2.提交多个任务
 */
public class MyTest {
    public static void main(String[] args) {
//        1.创建线程池对象
        MyThreadPool pool = new MyThreadPool(2,4,20);

//        2.提交多个任务
        for(int i=0;i<10;i++){
//        3. 创建任务对象,并提交给线程池
           MyTask myTask = new MyTask(i);
           pool.submit(myTask);
        }

    }
}

image-20230426155202107

四、 Java内置线程池 - ExecutorService 介绍

ExecutorService 接口是Java内置的线程池接口。

接口是无法直接创建对象的。

4.1 常用方法

  • void shutdown()

启动一次顺序关闭,执行以前提交的任务,但不接受新任务,即无法向线程池中提交任务,但是之前提交的任务会继续执行

  • List shutdownNow ( )

    停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表(使用返回值的)

  • Future submit(Callable task )

    方法重载

    执行带返回值的任务,返回一个Future对象.

  • Future<?> submit(Runnable task)

    方法重载

    执行 Runnable 任务,并返回一个表示该任务的 Future。

  • Future submit ( Runnable task,T result)

    方法重载

    执行 Runnable 任务,并返回一个表示该任务的 Future。

4.2 ExecutorService 获取

ExecutorService 是一个接口,无法直接创建,但是可以利用JDK中的Executors类(工厂类)中的静态方法来获取ExecutorService 对象

4.2.1 static ExecutorService newCachedThreadPool ( )

创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建

此方法获取的线程池的最大空闲时间是60秒

这个方法对线程的数量不做限制的,有多少任务,就创建多少线程,即优先执行任务,效率优先

/**
 * 练习Executors获取ExecutorService,然后调用方法提交任务
 */
public class MyTest01 {
    public static void main(String[] args) {
//      1.使用工厂类获取线程池对象
        ExecutorService executorService = Executors.newCachedThreadPool();
//      2. 提交任务:
        for (int i=0; i<10;i++){
            MyRunnable myRunnable = new MyRunnable(i);
            executorService.submit(myRunnable);
        }
    }
}

/**
 * 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
 */
class MyRunnable implements Runnable{

    private int id;

    @Override
    public void run() {
//     获取线程的名称,打印一句话
        String name = Thread.currentThread().getName();
        System.out.println(name+"执行了任务...."+id);
    }

    public MyRunnable(int id) {
        this.id = id;
    }
}

image-20230426170846365

4.2.2 static ExecutorService newCachedThreadPool (ThreadFactory threadFactory)

**指定线程的创建方式 **

线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行

ThreadFactory 也是一个接口,只不过允许程序员自己写实现类,在实现类内部创建线程对象 ,相当于程序员可以控制线程池中每一个线程对象的创建

/**
 * 练习Executors获取ExecutorService,然后调用方法提交任务
 */
public class MyTest01 {
    public static void main(String[] args) {
//      1.使用工厂类获取线程池对象
        ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() {
            int n=1;
            @Override
            public Thread newThread(Runnable r) {
//              创建的线程和任务r绑定在一起,就可以执行了
                return new Thread(r,"自定义的线程名称"+n++);
            }
        });
//      2. 提交任务:
        for (int i=0; i<10;i++){
            MyRunnable myRunnable = new MyRunnable(i);
            executorService.submit(myRunnable);
        }
    }
}

/**
 * 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
 */
class MyRunnable implements Runnable{

    private int id;

    @Override
    public void run() {
//     获取线程的名称,打印一句话
        String name = Thread.currentThread().getName();
        System.out.println(name+"执行了任务...."+id);
    }

    public MyRunnable(int id) {
        this.id = id;
    }
}

image-20230426171245502

4.2.3 static ExecutorService newFixedThreadPool(int nThreads)

创建一个可重用固定线程数的线程池,可以规定线程数量

​ 在创建线程池的时候指定线程池中线程的数量,降低服务器的压力。

​ 当我们任务特别多,任务放到缓存中,不会创建更多的线程来执行任务

/**
 * 练习Executors获取ExecutorService,然后调用方法提交任务
 */
public class MyTest02 {
    public static void main(String[] args) {
//      1.使用工厂类获取线程池对象   此时线程池中最多有三个线程
        ExecutorService executorService = Executors.newFixedThreadPool(3);
//      2. 提交任务:
        for (int i=0; i<10;i++){
            MyRunnable02 myRunnable = new MyRunnable02(i);
            executorService.submit(myRunnable);
        }
    }
}

/**
 * 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
 */
class MyRunnable02 implements Runnable{

    private int id;

    @Override
    public void run() {
//     获取线程的名称,打印一句话
        String name = Thread.currentThread().getName();
        System.out.println(name+"执行了任务...."+id);
    }

    public MyRunnable02(int id) {
        this.id = id;
    }
}

pool-1-thread-1执行了任务…0
pool-1-thread-3执行了任务…2
pool-1-thread-2执行了任务…1
pool-1-thread-3执行了任务…4
pool-1-thread-1执行了任务…3
pool-1-thread-3执行了任务…6
pool-1-thread-2执行了任务…5
pool-1-thread-3执行了任务…8
pool-1-thread-1执行了任务…7
pool-1-thread-2执行了任务…9

4.2.4 static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建

​ 与上边那个方法而言,此方法可以控制线程的创建

/**
 * 练习Executors获取ExecutorService,然后调用方法提交任务
 */
public class MyTest02 {
    public static void main(String[] args) {
//      1.使用工厂类获取线程池对象   此时线程池中最多有三个线程
        ExecutorService executorService = Executors.newFixedThreadPool(3, new ThreadFactory() {
            int n=1;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"自定义线程名称"+n++);
            }
        });
//      2. 提交任务:
        for (int i=0; i<10;i++){
            MyRunnable02 myRunnable = new MyRunnable02(i);
            executorService.submit(myRunnable);
        }
    }
}

/**
 * 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
 */
class MyRunnable02 implements Runnable{

    private int id;

    @Override
    public void run() {
//     获取线程的名称,打印一句话
        String name = Thread.currentThread().getName();
        System.out.println(name+"执行了任务...."+id);
    }

    public MyRunnable02(int id) {
        this.id = id;
    }
}

自定义线程名称3执行了任务…2
自定义线程名称1执行了任务…0
自定义线程名称2执行了任务…1
自定义线程名称3执行了任务…3
自定义线程名称2执行了任务…4
自定义线程名称3执行了任务…6
自定义线程名称2执行了任务…7
自定义线程名称1执行了任务…5
自定义线程名称2执行了任务…9
自定义线程名称3执行了任务…8

4.2.5 static ExecutorService newSingleThreadExecutor()

创建一个使用单个 worker 线程的 Executor,以无界队列方式(任务缓存的时候不限制数量)来运行该线程。

此种方法,只追求安全,不考虑性能

/**
 * 练习Executors获取ExecutorService,然后调用方法提交任务
 */
public class MyTest03 {
    public static void main(String[] args) {
//      1.使用工厂类获取线程池对象
        ExecutorService executorService = Executors.newSingleThreadExecutor();
//      2. 提交任务:
        for (int i=0; i<10;i++){
            MyRunnable03 myRunnable = new MyRunnable03(i);
            executorService.submit(myRunnable);
        }
    }
}

/**
 * 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
 */
class MyRunnable03 implements Runnable{

    private int id;

    @Override
    public void run() {
//     获取线程的名称,打印一句话
        String name = Thread.currentThread().getName();
        System.out.println(name+"执行了任务...."+id);
    }

    public MyRunnable03(int id) {
        this.id = id;
    }
}

pool-1-thread-1执行了任务…0
pool-1-thread-1执行了任务…1
pool-1-thread-1执行了任务…2
pool-1-thread-1执行了任务…3
pool-1-thread-1执行了任务…4
pool-1-thread-1执行了任务…5
pool-1-thread-1执行了任务…6
pool-1-thread-1执行了任务…7
pool-1-thread-1执行了任务…8
pool-1-thread-1执行了任务…9

4.2.6 static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)

创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。

/**
 * 练习Executors获取ExecutorService,然后调用方法提交任务
 */
public class MyTest03 {
    public static void main(String[] args) {
//      1.使用工厂类获取线程池对象
        ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
            int n=1;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"自定义线程名称"+n++);
            }
        });
//      2. 提交任务:
        for (int i=0; i<10;i++){
            MyRunnable03 myRunnable = new MyRunnable03(i);
            executorService.submit(myRunnable);
        }
    }
}

/**
 * 任务类:包含一个任务编号,在任务中打印出是哪一个线程正在执行任务
 */
class MyRunnable03 implements Runnable{

    private int id;

    @Override
    public void run() {
//     获取线程的名称,打印一句话
        String name = Thread.currentThread().getName();
        System.out.println(name+"执行了任务...."+id);
    }

    public MyRunnable03(int id) {
        this.id = id;
    }
}

自定义线程名称1执行了任务…0
自定义线程名称1执行了任务…1
自定义线程名称1执行了任务…2
自定义线程名称1执行了任务…3
自定义线程名称1执行了任务…4
自定义线程名称1执行了任务…5
自定义线程名称1执行了任务…6
自定义线程名称1执行了任务…7
自定义线程名称1执行了任务…8
自定义线程名称1执行了任务…9

五、Java内置线程池 - ScheduledExecutorService

ScheduledExecutorService 是 ExecutorService 的子接口

ScheduledExecutorService 具备了延迟运行或定期执行任务的能力

5.1 ScheduledExecutorService 获取

5.1.1 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务.(线程的数量是固定的)

/**
 * 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
 */
public class ScheduledExecutorServiceDemo01 {
    public static void main(String[] args) {
//      1.具备延迟执行任务的线程池对象  (线程池里面最多有三个)
        ScheduledExecutorService es = Executors.newScheduledThreadPool(3);

//      2.创建多个任务对象,提交任务,每个任务延迟执行
//        es.schedule(new MyRunnable(1),2, TimeUnit.SECONDS);
        for (int i = 1; i < 10; i++) {
            es.schedule(new MyRunnable(i), 2, TimeUnit.SECONDS);
        }
//      main方法结束标志
        System.out.println("over");
    }
}

class MyRunnable implements Runnable {

    private int id;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + "执行了任务编号" + id);
    }

    public MyRunnable(int id) {
        this.id = id;
    }

}

over大概输出2秒后,控制台才输出其他内容

over
pool-1-thread-1执行了任务编号1
pool-1-thread-2执行了任务编号2
pool-1-thread-3执行了任务编号3
pool-1-thread-3执行了任务编号6
pool-1-thread-3执行了任务编号7
pool-1-thread-2执行了任务编号5
pool-1-thread-1执行了任务编号4
pool-1-thread-2执行了任务编号9
pool-1-thread-3执行了任务编号8

5.1.2 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

可以指定线程工厂,从工厂里面得到线程对象

创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务;

5.1.3 static ScheduledExecutorService newSingleThreadScheduledExecutor()

创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行(单线程,但是允许延期运行、定期运行)

5.1.4 static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)

可以指定线程工厂,从工厂里面得到线程对象

创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
 */
public class ScheduledExecutorServiceDemo02 {
    public static void main(String[] args) {
//      1.具备延迟执行任务的线程池对象  (线程池里面最多有三个)
        ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            int num=1;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"自定义线程名"+num++);
            }
        });

//      2.创建多个任务对象,提交任务,每个任务延迟执行
        es.scheduleWithFixedDelay(new MyRunnable02(1), 2, 2,TimeUnit.SECONDS);

//      main方法结束标志
        System.out.println("over");
    }
}

class MyRunnable02 implements Runnable { 

    private int id;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        try {
//          模拟任务的执行时间比较长
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(name + "执行了任务编号" + id);
    }

    public MyRunnable02(int id) {
        this.id = id;
    }

}

5.2 ScheduledExecutorService 常用方法

ScheduledExecutorService常用方法如下

5.2.1 ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit)

​ 延迟时间单位是unit,数量是delay的时间后执行callable。

5.2.2 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

​ 延迟时间单位是unit,数量是delay的时间后执行command。

创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务.(线程的数量是固定的)

/**
 * 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
 */
public class ScheduledExecutorServiceDemo01 {
    public static void main(String[] args) {
//      1.具备延迟执行任务的线程池对象  (线程池里面最多有三个)
        ScheduledExecutorService es = Executors.newScheduledThreadPool(3);

//      2.创建多个任务对象,提交任务,每个任务延迟执行
//        es.schedule(new MyRunnable(1),2, TimeUnit.SECONDS);
        for (int i = 1; i < 10; i++) {
            es.schedule(new MyRunnable(i), 2, TimeUnit.SECONDS);
        }
//      main方法结束标志
        System.out.println("over");
    }
}

class MyRunnable implements Runnable {

    private int id;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name + "执行了任务编号" + id);
    }

    public MyRunnable(int id) {
        this.id = id;
    }

}

over大概输出2秒后,控制台才输出其他内容

over
pool-1-thread-1执行了任务编号1
pool-1-thread-2执行了任务编号2
pool-1-thread-3执行了任务编号3
pool-1-thread-3执行了任务编号6
pool-1-thread-3执行了任务编号7
pool-1-thread-2执行了任务编号5
pool-1-thread-1执行了任务编号4
pool-1-thread-2执行了任务编号9
pool-1-thread-3执行了任务编号8

5.2.3 ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

​ 延迟时间单位是unit,数量是initialDelay的时间后(先延迟initialDelay时间),每间隔period时间重复执行一次command。(第一次开始到第二次开始之间的时间差)

/**
 * 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
 */
public class ScheduledExecutorServiceDemo01 {
    public static void main(String[] args) {
//      1.具备延迟执行任务的线程池对象  (线程池里面最多有三个)
        ScheduledExecutorService es = Executors.newScheduledThreadPool(3, new ThreadFactory() {
            int num=1;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"自定义线程名"+num++);
            }
        });

//      2.创建多个任务对象,提交任务,每个任务延迟执行
            es.scheduleAtFixedRate(new MyRunnable(1), 2, 2,TimeUnit.SECONDS);

//      main方法结束标志
        System.out.println("over");
    }
}

class MyRunnable implements Runnable {

    private int id;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        try {
//          模拟任务的执行时间比较长
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(name + "执行了任务编号" + id);
    }

    public MyRunnable(int id) {
        this.id = id;
    }

}

over
自定义线程名1执行了任务编号1
自定义线程名1执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1
自定义线程名2执行了任务编号1

5.2.4 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)

​ 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。(第一次结束到下一次开始之间的时间差)

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

/**
 * 测试ScheduledExecutorService接口中延迟执行任务和重复执行任务功能
 */
public class ScheduledExecutorServiceDemo02 {
    public static void main(String[] args) {
//      1.具备延迟执行任务的线程池对象  (线程池里面最多有三个)
        ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
            int num=1;
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"自定义线程名"+num++);
            }
        });

//      2.创建多个任务对象,提交任务,每个任务延迟执行
        es.scheduleWithFixedDelay(new MyRunnable02(1), 2, 2,TimeUnit.SECONDS);

//      main方法结束标志
        System.out.println("over");
    }
}

class MyRunnable02 implements Runnable { 

    private int id;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        try {
//          模拟任务的执行时间比较长
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(name + "执行了任务编号" + id);
    }

    public MyRunnable02(int id) {
        this.id = id;
    }

}

六、 Future - 异步计算结果

​ 刚刚在java内置线程池使用时,没有考虑线程计算的结果,但开发中有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future对象获取线程计算的结果。

​ 在业务中用多线程计算某个任务,但是必须等待线程的执行结果才能往下进行(要使用带返回值的线程,否则怎么样也获取不到某些结果)。

6.1 常用方法

boolean cancel(boolean maylnterruptlfRunning)

​ 试图取消对此任务的执行。(任务正在执行或者还没有执行的时候 )

V get ()
如有必要,等待计算完成,然后获取其结果

V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)

boolean isCancelled()
如果在任务正常完成前将其取消,则返回 true。

boolean isDone()
如果任务已完成,则返回 true。

6.2 Future演示

**
 *   练习异步计算结果
 */
public class FutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//      1. 获取一个线程池对象
        ExecutorService es = Executors.newCachedThreadPool();

//      2. 创建Callable类型的任务对象
        Future<Integer> submit = es.submit(new MyCall(2, 5));

//      3.  判断任务是否已经完成
        boolean done = submit.isDone();
        System.out.println("第一次判断任务是否完成:"+done);

//      4.判断任务是否已经被取消
        boolean cancelled = submit.isCancelled();
        System.out.println("第一次判断任务是否取消:"+cancelled);

//      5.无限期等待结果,  直到完成为止
        Integer integer = submit.get();
        System.out.println("任务执行的结果是:"+integer);

         done = submit.isDone();
        System.out.println("第二次判断任务是否完成:"+done);

        cancelled = submit.isCancelled();
        System.out.println("第二次判断任务是否取消:"+cancelled);

//        *************************************
    }
}

class MyCall implements Callable<Integer>{

    private  int a;
    private  int b;


    @Override
    public Integer call() throws Exception {
        String name = Thread.currentThread().getName();
        System.out.println(name+"准备开始....");
        Thread.sleep(2000);
        System.out.println(name+"计算完成....");
        return a+b;
    }

    public MyCall(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

七、 综合案例

7.1 案例

案例介绍:

​ 假如某网上商城推出活动,新上架10部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢假如有20人同时参与了该活动,请使用线程池模拟这个场景,保证前10人秒杀成功,后10人秒杀失败

要求:

​ 1:使用线程池创建线程
​ 2:解决线程安全问题

思路提示:

​ 1:既然商品总数量是10个,那么我们可以在创建线程池的时候初始化线程数是10个及以下,设计线程池最大数量为10个

​ 2:当某个线程执行完任务之后,可以让其他秒杀的人继续使用该线程参与秒杀;

​ 3:使用svnchronized控制线程安全防止出现错误数据

代码步骤:

​ 1:编写任务类,主要是送出手机给秒杀成功的客户
​ 2:编写主程序类创建20个任务(模拟20个客户);
​ 3.创建线程池对象并接收20个任务开始执行任务

7.2 代码

public class MyTest {
    public static void main(String[] args) {
//       1. 创建一个线程池对象
//         核心线程3,最大线程数量5 ,最大空闲时间1分钟,任务队列里面最多缓存15个任务
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(15));

//      2.创建任务对象
        for (int i = 1; i <= 20; i++) {
            MyTask myTask = new MyTask("客户"+i);
            pool.submit(myTask);
        }

//      3.关闭线程池
        pool.shutdown();
    }
}
/**
 * 任务类:
 *    包含商品数量,客户名称,送手机的行为
 */
public class MyTask implements Runnable{

//  设计一个变量,用于表示商品的数量
    private static int num=10;

//  客户名称
    private String userName;

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(userName+"正在使用"+name+"参与秒杀任务......");

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

//      使用字节码文件作为锁对象
        synchronized (MyTask.class){
             if (num>0){
                 System.out.println(userName+"正在使用"+name+"秒杀了"+ --num +"商品");
             }else {
                 System.out.println(userName+"秒杀失败");
             }
        }
    }

    public MyTask(String userName) {
        this.userName = userName;
    }
}

客户2正在使用pool-1-thread-2参与秒杀任务…
客户20正在使用pool-1-thread-5参与秒杀任务…
客户3正在使用pool-1-thread-3参与秒杀任务…
客户19正在使用pool-1-thread-4参与秒杀任务…
客户1正在使用pool-1-thread-1参与秒杀任务…
客户3正在使用pool-1-thread-3秒杀了9商品
客户20正在使用pool-1-thread-5秒杀了8商品
客户19正在使用pool-1-thread-4秒杀了7商品
客户4正在使用pool-1-thread-5参与秒杀任务…
客户5正在使用pool-1-thread-4参与秒杀任务…
客户6正在使用pool-1-thread-3参与秒杀任务…
客户1正在使用pool-1-thread-1秒杀了6商品
客户2正在使用pool-1-thread-2秒杀了5商品
客户7正在使用pool-1-thread-1参与秒杀任务…
客户8正在使用pool-1-thread-2参与秒杀任务…
客户8正在使用pool-1-thread-2秒杀了4商品
客户7正在使用pool-1-thread-1秒杀了3商品
客户6正在使用pool-1-thread-3秒杀了2商品
客户5正在使用pool-1-thread-4秒杀了1商品
客户10正在使用pool-1-thread-1参与秒杀任务…
客户9正在使用pool-1-thread-2参与秒杀任务…
客户12正在使用pool-1-thread-4参与秒杀任务…
客户4正在使用pool-1-thread-5秒杀了0商品
客户11正在使用pool-1-thread-3参与秒杀任务…
客户13正在使用pool-1-thread-5参与秒杀任务…
客户11秒杀失败
客户10秒杀失败
客户14正在使用pool-1-thread-3参与秒杀任务…
客户15正在使用pool-1-thread-1参与秒杀任务…
客户9秒杀失败
客户12秒杀失败
客户16正在使用pool-1-thread-2参与秒杀任务…
客户17正在使用pool-1-thread-4参与秒杀任务…
客户13秒杀失败
客户18正在使用pool-1-thread-5参与秒杀任务…
客户17秒杀失败
客户15秒杀失败
客户16秒杀失败
客户14秒杀失败
客户18秒杀失败

使用字节码文件作为锁对象
synchronized (MyTask.class){
if (num>0){
System.out.println(userName+“正在使用”+name+“秒杀了”+ --num +“商品”);
}else {
System.out.println(userName+“秒杀失败”);
}
}
}

public MyTask(String userName) {
    this.userName = userName;
}

}




> 客户2正在使用pool-1-thread-2参与秒杀任务......
> 客户20正在使用pool-1-thread-5参与秒杀任务......
> 客户3正在使用pool-1-thread-3参与秒杀任务......
> 客户19正在使用pool-1-thread-4参与秒杀任务......
> 客户1正在使用pool-1-thread-1参与秒杀任务......
> 客户3正在使用pool-1-thread-3秒杀了9商品
> 客户20正在使用pool-1-thread-5秒杀了8商品
> 客户19正在使用pool-1-thread-4秒杀了7商品
> 客户4正在使用pool-1-thread-5参与秒杀任务......
> 客户5正在使用pool-1-thread-4参与秒杀任务......
> 客户6正在使用pool-1-thread-3参与秒杀任务......
> 客户1正在使用pool-1-thread-1秒杀了6商品
> 客户2正在使用pool-1-thread-2秒杀了5商品
> 客户7正在使用pool-1-thread-1参与秒杀任务......
> 客户8正在使用pool-1-thread-2参与秒杀任务......
> 客户8正在使用pool-1-thread-2秒杀了4商品
> 客户7正在使用pool-1-thread-1秒杀了3商品
> 客户6正在使用pool-1-thread-3秒杀了2商品
> 客户5正在使用pool-1-thread-4秒杀了1商品
> 客户10正在使用pool-1-thread-1参与秒杀任务......
> 客户9正在使用pool-1-thread-2参与秒杀任务......
> 客户12正在使用pool-1-thread-4参与秒杀任务......
> 客户4正在使用pool-1-thread-5秒杀了0商品
> 客户11正在使用pool-1-thread-3参与秒杀任务......
> 客户13正在使用pool-1-thread-5参与秒杀任务......
> 客户11秒杀失败
> 客户10秒杀失败
> 客户14正在使用pool-1-thread-3参与秒杀任务......
> 客户15正在使用pool-1-thread-1参与秒杀任务......
> 客户9秒杀失败
> 客户12秒杀失败
> 客户16正在使用pool-1-thread-2参与秒杀任务......
> 客户17正在使用pool-1-thread-4参与秒杀任务......
> 客户13秒杀失败
> 客户18正在使用pool-1-thread-5参与秒杀任务......
> 客户17秒杀失败
> 客户15秒杀失败
> 客户16秒杀失败
> 客户14秒杀失败
> 客户18秒杀失败





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

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

相关文章

[OtterCTF 2018]之Misc篇(NSSCTF)刷题记录⑦

NSSCTF-Misc篇-[OtterCTF 2018] [OtterCTF 2018]General Info[OtterCTF 2018]Play Time[OtterCTF 2018]Silly Rick[OtterCTF 2018]What the password?[OtterCTF 2018]Name Game[OtterCTF 2018]Hide And Seek[OtterCTF 2018]Name Game 2[OtterCTF 2018]Path To Glory[OtterCTF …

2023年第二十届五一数学建模竞赛C题:“双碳”目标下低碳建筑研究-思路详解与代码答案

该题对于模型的考察难度较低&#xff0c;难度在于数据的搜集以及选取与处理。 这里推荐数据查询的网站&#xff1a;中国碳核算数据库&#xff08;CEADs&#xff09; https://www.ceads.net.cn/ 国家数据 国家数据​data.stats.gov.cn/easyquery.htm?cnC01 以及各省市《统…

安陆EGS20 SDRAM仿真

目录 一. 搭建仿真平台 二. 实现SDRAM连续写入1024个数据&#xff0c;然后再连续读出&#xff0c;并比较 1. 调试过程中问题&#xff1a; 2. 顶层代码 3. 功能代码 三. SDRAMFIFO实现上述功能调试 1. 代码设计要点 2. 仿真过程问题 3. 上板运行调试 安陆反馈&#xf…

YOLOv6 4.0 使用记录: OpenCV DNN C++推理

目录 1、下载源码 2、下载权重文件 3、配置环境 4、推理 6、ONNX格式导出 权重文件为yolov6list_s.pt 权重为yolov6.pt 7、opencv DNN推理 8、个人总结 1、下载源码 下载最新的4.0版本的 2、下载权重文件 我下的是YOLOv6Lite-S 3、配置环境 cd到项目目录&#xff0c;运…

3.6 cache存储器

学习步骤&#xff1a; 我会采取以下几个步骤来学习Cache存储器&#xff1a; 确定学习目标&#xff1a;Cache存储器作为一种高速缓存存储器&#xff0c;通常用于提高计算机系统的运行效率。因此&#xff0c;我需要明确学习Cache存储器的目的&#xff0c;包括了解其原理、结构和…

一图看懂 requests 模块:用Python编写、供人类使用的HTTP库, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 requests 模块&#xff1a;用Python编写、供人类使用的HTTP库, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【requests】统计常量str 模块3 w…

java数据结构之HashMap

目录 前言 1、初始化 1.1、初始化 1.2、插入第一条数据 2、数组 链表 2.1、插入数据&#xff1a;没有hash冲突 2.2、插入数据&#xff1a;Key不同&#xff0c;但产生hash冲突 2.3、插入数据&#xff1a;Key相同 3、数组 红黑树 3.1、链表如何转化为红黑树&#xff1f; 3.…

golang - switch

switch 的使用 switch 语句用于基于不同条件执行不同操作&#xff0c;&#xff0c;直每一个 case 分支都是唯一的&#xff0c;从上到下逐一测试到匹配为止匹配项后面也不需要再加 break switch 表达式 {case 表达式1, 表达式2, ... :语句块1case 表达式2, 表达式3, ... :语句块…

GPT:你知道这五年我怎么过的么?

时间轴 GPT 首先最初版的GPT&#xff0c;来源于论文Improving Language Understanding by Generative Pre-Training&#xff08;翻译过来就是&#xff1a;使用通用的预训练来提升语言的理解能力&#xff09;。GPT这个名字其实并没有在论文中提到过&#xff0c;后人将论文名最后…

【Unity3D小功能】Unity3D中实现轮船在水面上移动效果

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 标题是啥我写啥&#xff0c;大家好&#xff0c;今天给大家带来…

你的 Kubernetes 安全吗?最新benchmark的重要趋势解读

导语 疫情过后经济处在缓慢复苏的阶段&#xff0c;对于企业应该优先考虑数字化转型&#xff0c;因为它可以促进增长和创新。 不可避免地&#xff0c;当今的数字化转型计划依赖于云的可扩展性和灵活性。 虽然在云中启动应用程序和服务带来了许多机遇&#xff0c;但也带来了新的…

云原生Istio架构和组件介绍

目录 1 Istio 架构2 Istio组件介绍2.1 Pilot2.2 Mixer2.3 Citadel2.4 Galley2.5 Sidecar-injector2.6 Proxy(Envoy)2.7 Ingressgateway2.8 其他组件 1 Istio 架构 Istio的架构&#xff0c;分为控制平面和数据面平两部分。 - 数据平面&#xff1a;由一组智能代理&#xff08;[En…

HCIA-RS实验-路由配置-静态路由缺省路由(2)

接上文HCIA-RS实验-路由配置-静态路由&缺省路由 继续完成缺省路由&#xff1b;其他原截图就不再一一截图&#xff0c;有需要往回看一篇。 关闭上一篇的接口shutdown&#xff08;重新启动&#xff09; 上一篇在R2关闭的接口2 需要重新启动&#xff0c;输入 undo shutdown…

4月VR大数据:PICO平台应用近400款,领跑国内VR生态

Hello大家好&#xff0c;每月一期的VR内容/硬件大数据统计又和大家见面了。 想了解VR软硬件行情么&#xff1f;关注这里就对了。我们会统计Steam平台的用户及内容等数据&#xff0c;每月初准时为你推送&#xff0c;不要错过喔&#xff01; 本数据报告包含&#xff1a;Steam VR硬…

我们公司的面试,有点不一样!

我们公司的面试&#xff0c;有点不一样&#xff01; 朋友们周末愉快&#xff0c;我是鱼皮。因为我很屑&#xff0c;所以大家也可以叫我屑老板。 自从我发了自己创业的文章和视频后&#xff0c;收到了很多小伙伴们的祝福&#xff0c;真心非常感谢&#xff01; 不得不说&#…

Elasticsearch:人类语言到 Elasticsearch 查询 DSL

Elasticsearch 为开发者提供了强大的搜索功能。Elasticsearch 使用 DSL 来进行查询。对于很多从关系数据库过来的人&#xff0c;这个很显然不很适应。虽然我们可以使用 SQL 来进行查询&#xff0c;但是我们必须通过一些命令来进行转换。我们可以通过阅读文章&#xff1a; Elast…

【Java面试八股文】数据库篇

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线MySQL高级篇设计模式牛客面试题 目录 请你说说MySQL索引,以及它们的好处和坏处 请你说说MySQL的索引是什么结构,为什么不用哈希表 请你说说数据库索引的底…

Segmentation of retinal vessels based on MRANet

随手把一篇论文的创新部分抽取出来 MLF 为了更好地聚合每一层的上采样特征信息和MSR块的信息&#xff0c;在解码路径中使用了MLF块&#xff0c;这允许最大限度地重用功能&#xff0c;从而减少细节的损失。MLF块的结构如图2所示。 如图2所示&#xff0c;有两种输入:input1和inp…

观察 | 卫浴产业数字化转型下的中国智造样本

文 | 智能相对论 作者 | 佘凯文 数字技术的发展已成为全球科技变革向高端技术不断升级的方向。 年初&#xff0c;中共中央、国务院印发《数字中国建设整体布局规划》&#xff0c;这是党的二十大后党中央在我国数字化发展领域作出的最全面擘画&#xff0c;从顶层设计的高度对…

ETL工具 - Kettle 介绍及基本使用

一、Kettle 介绍 在介绍 Kettle 前先了解下什么是 ETL&#xff0c;ETL是 Extract-Transform-Load 的缩写&#xff0c;即数据 抽取、转换、装载 的过程&#xff0c;对于企业或行业应用来说&#xff0c;经常会遇到各种异构数据的处理、转换、迁移等操作&#xff0c;这些操作有可…