程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

java多线程编程,面试真的躲不开!

balukai 2025-03-05 13:20:49 文章精选 7 ℃

1. JUC 编程

我们常说的 java 并发编程,别名叫做 JUC 编程。

因为 java 并发编程中常见的工具类例如 ReentrantLock、Callable、Executor 等都在 java.util.concurrent 这个包下面,所以 java 并发编程又叫做 JUC 编程。

所谓的 java 并发编程,其实就是在高并发的场景下,通过使用 java.util.concurrent 包下面的接口或者工具类来提高程序的处理速度,并且保证数据的安全性。

2. 为何要学习高并发编程?

  • 1. 在 Java 开发岗的职位要求中,并发编程技术属于必会项。

  • 2. 我们在日常生活中用到的微信、淘宝、抖音等亿级用户软件都需要处理大量的并发请求。多线程技术能够提高系统的吞吐量,确保系统在高负载下仍能保持良好的性能和稳定性,满足用户的需求。
  • 3. 多线程技术可以充分利用 CPU 的资源,加快系统响应时间,提升系统的性能。

3. 进程和线程

3.1 进程

  • 进程是操作系统资源分配的基本单位。
  • 每个进程都有独立的内存空间和系统资源,这意味着进程之间的资源是相互独立的。
  • 进程的创建和销毁开销较大,因为涉及到内存空间的分配和回收。
  • 进程之间可以通过进程间通信机制进行通信

通俗地讲,进程可以看做电脑上正在运行的软件,例如微信、qq 等。

打开电脑的任务管理器,每一个正在运行的软件可以看做一个进程:

3.2 线程

  • 线程是CPU调度的基本单位,是程序执行的最小单位。
  • 线程存在于进程之中,同一进程内的线程共享进程的资源和内存空间。
  • 线程的创建和销毁开销较小,因为它们共享同一进程的资源。
  • 线程之间的通信非常高效,因为它们可以直接访问共享的内存和数据。

通俗地讲:我们打开浏览器之后,可以一边下载文件,一边浏览网页,一边安装插件。在浏览器上我们可以同时使用很多功能,每一个功能就是一个线程。

3.3 进程和线程的区别

一个进程包含多个线程。

总体来说,进程和线程的主要区别在于资源分配和执行调度的单位不同。

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

3.4 CPU 的核心数和线程数的关系

先说结论:线程数和 CPU 核心数没有什么必然联系。

如果是单核 CPU,一个 CPU 同一时间只能运行一个线程。

如果有多个线程,单核 CPU 就会快速在多个线程之间切换。只要CPU 调用线程的时间够快,就好像同时在执行多个线程。

当然现在主流的电脑或者服务器都是多核心的,我们要想充分利用多核 CPU 的资源,一般来说设置的线程数不能少于核心数。

3.5 多线程的执行流程

创建线程之后,操作系统通过 CPU 调度机制来分配线程给 CPU 进行调度

也就是说我们在代码里面创建了包含任务的线程之后,java 编译器将 .java 文件编译成字节码文件。接着 java 虚拟机来运行这个字节码文件。

因为 java 虚拟机运行在电脑(服务器)的操作系统之上,所以操作系统会根据调度机制将线程分配给 CPU,CPU 调度线程就是去执行线程的任务(Runable)。

4. 并行和并发

4.1 并发

在单核 CPU 系统中,要同时执行多个程序,因为每个 CPU 每个时刻只能执行一个程序,所以需要交替执行。

因为执行的速度比较快,所以给人的感觉是同时执行多个程序。

通俗易懂:

一家饭店只有1个厨师,这时候来了多个顾客,厨师只能交替给这些顾客做饭。

4.2 并行

在多核 CPU 系统中,要同时执行多个程序。每个 CPU 可以执行一个程序,这样多个 CPU 可以同时执行多个程序,大大提高系统的运行效率。

通俗易懂:

一家饭店有多个厨师,这时候来了多个顾客。每个厨师可以单独给一个客户做饭,多个厨师可以同时给多个顾客做饭。

5. 创建线程的几种方式

5.1 继承 Thread 类

/**
 * @author 
 * @description 继承Thread类创建线程
 * @date 2024-09-12 21:25
 */
@Slf4j
public class MyThread extends Thread {

    @Override
    public void run() {
        log.info("任务1在运行:{}",Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
       
    }
}

5.2 实现 Runnable 接口

1.普通的创建方式

/**
 * @author 
 * @description 实现Runnable接口创建线程
 * @date 2024-09-12 21:25
 */
@Slf4j
public class NewRunnable implements Runnable{
    @Override
    public void run() {
        log.info("执行任务:{}",Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new NewRunnable());
        thread.start();
    }
}

2.匿名内部类方式

public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            log.info("执行任务");
        }
    });
    thread.start();
}

3.Lambda表达式

Thread thread = new Thread(() -> log.info("执行任务"));
thread.start();

5.3 使用 Callable 和 Future

/**
 * @author 
 * @description 使用 Callable 和 Future
 * @date 2024-09-13 19:50
 */
@Slf4j
public class MyCallableTask implements Callable {
    @Override
    public Integer call() {
        log.info("执行程序:{}",Thread.currentThread().getName());
        return 100;
    }
    public static void main(String[] args) {
        MyCallableTask myCallable = new MyCallableTask();
        FutureTask futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask,"MyCallable");
        thread.start();
        try {
            // 通过阻塞获得执行结果
            Integer result = futureTask.get();
            log.info("执行结果:{}",result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.4 线程池

这里只是写一个简单的例子,后面会详细介绍线程池的使用方式。

/**
 * @author 
 * @description 线程池创建线程
 * @date 2024-09-13 20:24
 */
@Slf4j
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for(int i=0;i<10;i++){ executor.execute->{
                log.info("执行任务:{}",Thread.currentThread().getName());
            });
        }
        executor.shutdown();
    }
}

6. 线程常用方法

6.1 线程休眠

sleep( ) 方法可以让线程休眠指定的毫秒数,在休眠结束的时候继续执行该线程;

public static void main(String[] args){
    int a = 1;
    int b = 2;
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("结果:{}", a + b);
}

6.2 线程优先级

setPriority( ) 方法来设置线程的优先级别,级别越高,优先被执行。

@Slf4j
public class MyThread extends Thread {
    @Override
    public void run() {
        log.info("任务在运行:{}",Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        MyThread myThread3 = new MyThread();
        myThread1.setName("线程1");
        myThread1.setPriority(10);
        myThread2.setPriority(2);
        myThread2.setName("线程2");
        myThread3.setPriority(5);
        myThread3.setName("线程3");
        myThread3.start();
        myThread1.start();
        myThread2.start();
    }
}

6.3 线程礼让

Thread.yield() 的作用是暂停当前正在执行的线程对象,去执行其他线程。

yield() 做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield() 的目的是让相同优先级的线程之间能适当执行。

yield()方法并不能保证线程一定会让出 CPU 资源,它只是一个提示,告诉调度器当前线程愿意 让出 CPU 资源。具体是否让出 CPU 资源,还是由调度器决定。

/**
 * @author 
 * @description 线程礼让
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args) {

        new Thread(()->{
            for (int i=0;i<10;i++){ log.infothread.currentthread.getname .start new thread->{
            for (int i=0;i<10;i++){
                log.info("啦啦啦啦啦:{}",Thread.currentThread().getName());
                Thread.yield();
            }
        }).start();
    }
}

6.4 线程打断

线程打断方法:

  • interrupt():仅仅是设置线程的中断状态为 true,并不是真的停止该线程。
  • interrupted():获取当前线程的中断状态,并且会清除线程的中断标记,是一个是静态方法。
  • isInterrupted():获取当前线程的中断状态。
/**
 * @author 
 * @description 线程中断
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) {
                //当前线程判断中断标志
                if (Thread.currentThread().isInterrupted()) {
                    log.info("程序中断------");
                    log.info("程序中断状态2:{}",Thread.currentThread().isInterrupted());
                    break;
                }
            }
        });
        log.info("程序中断状态1:{}",thread1.currentThread().isInterrupted());
        thread1.start();
        // 标记线程中断状态为:true
        thread1.interrupt();
        log.info("程序中断状态3:{}",thread1.interrupted());
    }
}

知否君总结:

1.所有线程都有一个默认的中断标志,值是 false。调用 interrupt 方法将该中断标志设置为 true。

2.isInterrupted 方法用来获取中断标志的值,如果是 true,就在业务层面进行真正的中断,例如通过 break 跳出 for 循环。

3.interrupted 清除中断标志,也就是将中断标志设置为默认值 false。

6.5 合并线程

B 线程需要用到 A 线程的处理结果。因为线程的执行过程是异步的,所以 B 线程需要等待 A 线程执行之再执行。

join( ) 方法可以让交替执行的线程按照顺序执行,简单理解就是同步

/**
 * @author 
 * @description 线程合并
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(4000);
                log.info("子线程处理任务完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.start();
        thread1.join();
        log.info("主线程处理任务完成");
    }
}

知否君总结:

join 的英文解释是加入

A 线程调用了 join() 方法,A 线程就成为了 CPU 的 VIP 客戶,拥有优先调度权,其他的线程必须在 A 后面排队等待。A 线程在完成任务之后,CPU 再去调度后面排队的线程。

6.6 守护线程

Java 中有两种线程:用户线程、守护线程。所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程。

当所有的非守护线程运行结束时,程序也就终止了,同时系统会杀死进程中的所有守护线程。

将用户线程转换为守护线程可以通过调用 Thread 对象的 setDaemon(true) 方法来实现。

/**
 * @author 
 * @description 守护线程
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                log.info("thread2线程处理任务:{}",i);
            }
        });
        // 线程2设置为守护线程
        thread2.setDaemon(true);
        thread2.start();
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            log.info("主线程处理任务:{}",i);
        }
    }
}

注:thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会抛出 IllegalThreadStateException 异常,因为你不能把正在运行的线程设置为守护线程。

7.线程的状态

  • New:初始状态,线程刚被创建,还没有调用 start()方法
  • Runnable:就绪状态,等待被CPU执行
  • Running:当被 CPU 调度之后,就进入运行中状态
  • Blocked:阻塞状态,当线程睡眠、等待、进行IO请求时就进入阻塞状态
  • Terminated:终止状态,表示当前线程已经执行完毕

8.线程池

8.1 为什么要用线程池

前面我们讲过多线程技术可以显著提高系统的并发处理能力,允许服务器同时处理多个客户端的请求,提高系统的吞吐量。

但是如果用户请求量非常大,频繁创建线程会大大消耗系统的资源。因为每创建一个新的线程就要开辟新的内存,操作系统在这一堆线程之间来回切换,系统性能会下降很快,也容易造成内存溢出。

那有没有办法我们不用创建一堆线程,只是创建几个线程,这几个线程可以复用,执行完任务之后不立即销毁,接着继续执行其他的任务呢?

有,线程池出现了。

线程池就是一个存放多个线程的容器,里面的线程可以重复利用。

如果处理的任务数量超过了线程的数量,超出的任务就放到队列里面排队等候。等线程执行完手头里的任务,再从队列里面取出其他任务继续执行。

8.2 java 创建线程池

8.2.1 Executors 工具类

1.newFixedThreadPool:创建一个固定度的线程池

/**
 * @author 
 * @description 多线程
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个固定长度的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5 i executorservice.execute->{
                log.info("执行任务:{}",Thread.currentThread().getName());
            });
        }
    }
}

2.newSingleThreadExecutor:创建只有一个线程的线程池

/**
 * @author 
 * @description 多线程
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        // 创建只有一个线程的线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5 i executorservice.execute->{
                log.info("执行任务:{}",Thread.currentThread().getName());
            });
        }
    }
}

2.newCachedThreadPool:创建一个线程数量可变的线程池

/**
 * @author 
 * @description 线程池
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args){
        // 创建可变线程数量的线程池,线程的数量会随着任务的增多而增多
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3 i executorservice.execute->{
                log.info("执行任务:{}",Thread.currentThread().getName());
            });
        }
    }
}

3.newScheduledThreadPool:创建一个可延迟执行的线程池

/**
 * @author 
 * @description 线程池
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args){
        // 创建可延迟执行的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        // 表示5秒后执行任务代码块
        scheduledExecutorService.schedule(() -> {
            log.info("延迟执行任务:{}",Thread.currentThread().getName() );
        }, 5, TimeUnit.SECONDS);
        // 表示5秒后执行代码块,每3秒再执行一次
        scheduledExecutorService.scheduleAtFixedRate(()->{
            log.info("延迟执行任务:{}",Thread.currentThread().getName() );
        },5,3, TimeUnit.SECONDS);
    }
}

8.2.2 ThreadPoolExecutor 类

/**
 * @author 
 * @description 线程池
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args){
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,  new ArrayBlockingQueue(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 3 i threadpool.execute->{
                log.info("延迟执行任务:{}",Thread.currentThread().getName());
            });
        }
    }
}

ThreadPoolExecutor 中的各个参数的意义:

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程存活时间。当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
  • unit:时间单位,默认60秒。
  • workQueue:阻塞队列
  • threadFactory:线程池工厂
  • RejectedExecutionHandler:拒绝策略

8.2.3 线程池执行任务的方法

前面案例中线程池执行任务用的 execute 方法,还有一个 submit 方法也可以用来执行任务。

submit()方法是定义 在 ExecutorService 接口中的,它允许开发人员提交一个 Callable 或 Runnable 对象给线程池来执行,有返回值。

而 execute() 方法是定义在 Executor 接口中的,只接收 Runnable 对象,并且没有返回值。

简单来说,submit()方法更加灵活,可以处理带返回值的任务,而 execute() 只能处理不带返回值的任务。

submit 方法案例:

/**
 * @author 
 * @description 线程池
 * @date 2024-09-14 8:55
 */
@Slf4j
public class MyThread {
    public static void main(String[] args){
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,  new ArrayBlockingQueue(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        Future future = threadPool.submit(() -> {
            log.info("执行任务:{}", Thread.currentThread().getName());
            return 100;
        });
        try {
            Integer result = future.get();
            log.info("线程池执行任务之后返回的结果:{}", result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        threadPool.shutdown();
    }
}

8.2.4 线程池拒绝策略

面试常考

  • AbortPolicy:这是默认的拒绝策略,当线程池无法处理新任务时,会抛出RejectedExecutionException异常,通知调用者任务被拒绝。
  • CallerRunsPolicy:该策略会将任务交还给提交任务的线程执行,如果线程池未关闭且没有能力执行任务,则由调用者线程处理,这可以防止任务丢失,但可能导致性能下降。
  • DiscardPolicy:该策略会直接丢弃无法处理的任务,不通知调用者,这可能导致数据丢失,但可以避免异常和性能下降。
  • DiscardOldestPolicy:当任务被拒绝时,线程池会丢弃队列中最旧的未执行的任务,然后将被拒绝的任务添加到等待队列当中。

8.2.4 关闭线程池

执行完任务之后,一定要记得关闭线程池

线程池的设计初衷是为了复用线程,避免频繁创建和销毁线程的开销,从而提高系统性能。然而,当线程池使用完毕后,如果不进行适当的关闭操作,可能会导致资源泄漏和内存溢出。

线程池提供了 2 个关闭方法:shutdown 和 shutdownNow。

shutdown 方法:线程池将不再接收新任务,内部会将所有已提交的任务处理完毕,处理完毕之后,工作线程自动退出。

shutdownNow 方法后:线程池会将还未处理的(在队里等待处理的任务)任务移除,将正在处理中的处理完毕之后,工作线程自动退出。

多数情况下调用 shutdown 方法关闭线程池。

8.3 线程池原理

这里我以银行办理业务的场景讲解线程池的原理:

我们先来初始化一个线程池:核心2个线程,最大4个线程,存放任务的队列数是3。

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS,  new ArrayBlockingQueue(3), Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 2 i threadpool.execute -> {
        log.info("办理业务====>:{}", Thread.currentThread().getName());
    });
}
// 关闭线程池
threadPool.shutdown();

相当于在一个银行里面总共有4个柜台,其中2个是常用的,另外2个是闲置的。等候区可以坐3个用户。

1.当执行的任务数小于等于核心线程数,只有核心线程来执行任务。相当于只有1号和2号窗口给用户办理业务。

2.当执行的任务数量超过核心线程数量,但是小于等于核心数量加队列的数量之和,超出核心线程数量的任务去队列排队等待,仍然是只有这2个核心线程处理任务。

相当于此时去银行办事的用户数量有5人。前2个用户去1号2号窗口办事,另外3个去等候区。等1号2号某个窗口没人了,等候区的用户再依次去1号或者2号窗口办事。这时候只有1号2号窗口给用户办理业务。

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(3), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 5; i++) {
threadPool.execute(() -> {
log.info("办理业务====>:{}", Thread.currentThread().getName());
});
}
// 关闭线程池
threadPool.shutdown();

3.当执行的任务数量超过核心数量加队列的数量之和,小于等于最大线程数和队列之和,这时候除了核心线程,另外的线程也会参与进来执行任务。

相当于此时去银行办事的用户数量有7人。前2个用户去1号2号窗口办事,另外3个去等候区。这时候银行会开启3号和4号窗口来给用户办理业务。

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(3), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i <7; i++) {
threadPool.execute(() -> {
log.info("办理业务====>:{}", Thread.currentThread().getName());
});
}
// 关闭线程池
threadPool.shutdown();

4.当执行的任务数量超过最大线程数和队列数之和,就会执行线程池的拒绝策略,默认是抛出异常。

相当于银行窗口和等候区之和是7,这时候来了10个人。保安就说乡亲们屋里坐不下了,明天再来吧,保安就把多出来的3个人赶走了。当然这只是举个例子,现实中银行办事不是这样。

9.Springboot 使用线程池案例

这里我们写一个用线程池保存10万用户信息到数据库的简单案例:

1.新建用户类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class SysUser {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
}

2.新建线程池配置类

/**
 * @author 
 * @description 线程池配置类
 * @date 2024-09-14 19:49
 */
@Configuration
@EnableAsync
public class ThreadPoolConfig {
    @Bean(name = "ThreadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);// 核心线程数
        executor.setMaxPoolSize(16);// 最大线程数
        executor.setQueueCapacity(200);// 队列容量
        executor.setKeepAliveSeconds(60); //线程空用时间
        executor.setThreadNamePrefix("MyThreadPoolExecutor-");// 线程名前缀
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 拒绝策略
        return executor;
    }
}

3.新建任务接口

/**
 * @author 
 * @description 异步任务接口
 * @date 2024-09-14 19:56
 */
public interface AsyncService {

    /**
     * 异步任务大批量保存用户信息
     * @param userList
     * @param sysUserService
     */
    void saveUserAsync(List userList,SysUserService sysUserService);
}

4.新建任务接口实现类

/**
 * @author 
 * @description 异步任务实现类
 * @date 2024-09-14 19:57
 */
@Service
public class AsyncServiceImpl implements AsyncService {
    @Async("ThreadPoolTaskExecutor")
    @Override
    public void saveUserAsync(List userList, SysUserService sysUserService) {
        try {
            // 异步线程要执行的任务
            sysUserService.saveBatch(userList);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

5.新建拆分大数据工具类

/**
 * @author 
 * @description 拆分大数据工具类
 * @date 2024-09-14 20:01
 */
public class SplitBigDataUtil {
    public static  List<List> SplitBigData(List bidDataList, int pageSize) {
        if (CollectionUtil.isEmpty(bidDataList) || pageSize <= 0) {
            return CollectionUtil.newArrayList();
        }
        int length = bidDataList.size();
        int num = (length + pageSize - 1) / pageSize;
        List<List> newDataList = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            int fromIndex = i * pageSize;
            int toIndex = (i + 1) * pageSize < length ? (i + 1) * pageSize : length;
            newDataList.add(bidDataList.subList(fromIndex, toIndex));
        }
        return newDataList;
    }
}

6.测试

    @Test
    void myTest() {
        List userList = new ArrayList<>();
        for (int i = 0; i < 100000; i++) {
            SysUser sysUser = new SysUser();
            sysUser.setName("哈哈哈"+i);
            sysUser.setAge(i);
            userList.add(sysUser);
        }
        // 拆分大数据,每个任务处理1000个数据
        List<List> lists = SplitBigDataUtil.SplitBigData(userList, 1000);
        for (List list : lists) {
            asyncService.saveUserAsync(list,sysUserService); 
        }
    }

Tags:

最近发表
标签列表