Java——使用多线程从list中不重复地取出数据并进行处理,给多线程任务添加单项任务计时和总耗时

一、最简版-无参数传递

1.创建业务类,实现Runnable接口

把处理数据的方法写在处理类中重写的Runnable接口的run()方法中。

import lombok.extern.slf4j.Slf4j;


import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Demo01Runnable implements Runnable {

    //实现父接口Runnable中的run方法
    @Override
    public void run() {
        //调用自己的业务方法
        doSomething();
    }

    //自己业务的方法
    private void doSomething() {
        log.info("随便干点啥...");
    }
}

2.创建线程,实例化自己创建的业务类并调用

@Slf4j
@Service
public class FileOperateService {

    // 业务处理的线程池(设置为4个线程)
    private static ExecutorService pool = Executors.newFixedThreadPool(4);

    //在这里调用
	public static void main(String[] args) {
        //创建一个Runnable对象,并将CountDownLatch的实例对象传入
        Demo01Runnable runnable = new Demo01Runnable();
        for (int i = 0; i < 4; i++) {
            //从线程池取出一个线程来执行业务,遍历4次就是取出4个
            pool.execute(runnable);
        }
    }
}

3.运行结果

如图所示可以看到创建了4个线程
在这里插入图片描述

二、加强版-有参数传递

大部分的业务都不是独立运行的,会有参数传递到业务中,如果需要多线程调用的业务有主线程的参数需要传递过去,就可以把参数写在实现了Runnable接口的业务类的私有属性中,用业务类的构造方法传参。具体代码改造如下:

1.创建业务类,实现Runnable接口

  • 添加了私有属性Integer num

  • 添加了num的构造方法

  • 在方法中调用了属性num

import lombok.extern.slf4j.Slf4j;


import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Demo02Runnable implements Runnable {
    private Integer num;

    public Demo02Runnable(Integer num){
        this.num = num;
    }

    //实现父接口Runnable中的run方法
    @Override
    public void run() {
        //调用自己的业务方法
        doSomething();
    }

    //自己业务的方法
    private void doSomething() {
        log.info("第 {} 个线程开始干活", num);
    }
}

2.创建线程,实例化自己创建的业务类并调用

  • new Demo02Runnable对象时候将num作为参数传递构造方法中
@Slf4j
@Service
public class FileOperateService {

    // 业务处理的线程池(设置为4个线程)
    private static ExecutorService pool = Executors.newFixedThreadPool(4);

    //在这里调用
	public static void main(String[] args) {

        for (int i = 0; i < 4; i++) {
            //创建一个Runnable对象,并将CountDownLatch的实例对象传入
            Demo02Runnable runnable = new Demo02Runnable(i);
            //从线程池取出一个线程来执行业务,遍历4次就是取出4个
            pool.execute(runnable);
        }
    }
}

3.运行结果

如图所示可以看到参数成功传递进去了

在这里插入图片描述

三、加加强版-有参数传递且带单项任务计时和总耗时

在业务里,使用多个线程并行处理业务能够极大的加快处理速度,但是如果我们需要明确掌握每次处理业务的耗时,和所有业务处理完成的总耗时,就可以通过CountDownLatch来帮我们实现。

1.创建业务类,实现Runnable接口

  • 添加了私有属性:

    • Integer size -用来记录任务的总数量
    • long ct -用来记录任务开始的时间
    • AtomicInteger taskProgress -用来记录任务执行的数量
    • CountDownLatch latch -用来统计单项任务计时和总耗时
  • 添加了以上属性的构造方法

  • 在方法中调用了以上属性

import lombok.extern.slf4j.Slf4j;


import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Demo03Runnable implements Runnable {
    private Integer size;
    private long ct;
    private AtomicInteger taskProgress;
    private CountDownLatch latch;
    
    public Demo03Runnable(Integer size, long ct, AtomicInteger taskProgress, CountDownLatch latch){
        this.size = size;
        this.ct = ct;
        this.taskProgress = taskProgress;
        this.latch = latch;
    }

    //实现父接口Runnable中的run方法
    @Override
    public void run() {
        //调用自己的业务方法
        doSomething();
    }

    //自己业务的方法
    private void doSomething() {
        //从系统获取业务开始执行时的时间
        long curr = System.currentTimeMillis();
        try {
            //线程睡0.5秒来代替任意业务逻辑
            Thread.sleep(500);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            //让CountDownLatch计数器减1
            latch.countDown();
            //统计任务数量,即本条业务被线程执行的次数(每执行一次就+1
            int task = taskProgress.getAndIncrement();
            log.info("任务进度 {} / {} = {}%\t {} 号任务耗时:{} - 任务总耗时:{} ", task, size, String.format("%.2f", task * 100.0 / size), task, System.currentTimeMillis() - curr, System.currentTimeMillis() - ct);

        }
    }
}

2.创建线程,实例化自己创建的业务类并调用

  • 任务开始前,创建CountDownLatch对象用于在指定位置控制所有子线程完成后再走主线程指定代码
  • 任务开始前,从系统获取当前时间作为任务开始时间
  • 任务开始前,创建AtomicInteger用于统计任务执行数量
  • 创建Demo03Runnable对象时候将size(任务总数量),ct(任务开始时间),taskProgress(任务执行数量),latch作为参数传递构造方法中
  • 主线程中调用子线程的方法结束后,调用await()方法,阻塞主线程,在这之后的代码只会在子线程全部执行完成之后,才开始执行。因此在这里再次获取系统当前时间,用于和前面记录的时间计算后统计相关耗时。
@Slf4j
@Service
public class FileOperateService {

    // 业务处理的线程池(设置为4个线程)
    private static ExecutorService pool = Executors.newFixedThreadPool(4);

    //在这里调用
	public static void main(String[] args) {

        //创建一个CountDownLatch对象,并调用构造器将计数器设置为总任务数量
        CountDownLatch latch = new CountDownLatch(10);
        //记录任务开始时间
        long ct = System.currentTimeMillis();
        //记录任务开始数量
        final AtomicInteger taskProgress = new AtomicInteger(1);
        for (int i = 1; i <= 10; i++) {
            //创建一个Runnable对象,并将CountDownLatch的实例对象传入
            Demo03Runnable runnable = new Demo03Runnable(10,ct,taskProgress,latch);
            pool.execute(runnable);
        }
        //调用await()方法,阻塞主线程,当上述启动的所有分线程都执行完后,主线程才会被放行
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //记录下结束时间
        log.info("任务全部执行完成,总耗时:{} 毫秒", System.currentTimeMillis() - ct);
    }
}

3.运行结果

如图所示

在这里插入图片描述

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值