Java线程池

本文详细介绍了Java中的线程池概念、ExecutorService的作用与使用方法,以及Callable接口和Future接口在获取并发任务结果中的应用,帮助开发者理解和优化并发编程性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. Java中的线程池

1.1 线程池概述

        在计算机编程中,线程池是一种软件设计模式,用于在计算机程序中实现并发执行。线程池维护多个线程,等待监督程序分配任务并并发执行。通过维护线程池,该模型提高了性能,并避免了由于频繁创建和销毁线程而导致的执行延迟。

        Java中的线程池是运用场景最多的并发框架,几乎所有需要异步(多线程各自执行各自的)或同步执行任务的程序都可以使用线程池。

        在开发过程中,合理地使用线程池的优势如下:

  • 降低资源消耗:通过复用已创建的线程来降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

1.2 ExecutorService

        ExecutorService是一个Java API,可以简化异步模式下运行的任务(Task)。一般来说,ExecutorService 会自动提供一个线程池和一个用于为其分配任务的API。

        ExecutorService名称中的Executor表示一个任务的执行器。开发者可以使用Runnable接口的实现类来封装线程的工作单元(线程启动后执行的具体逻辑),将该工作单元看作一个要被执行的任务,委派给Executor来执行。

        ExecutorService接口继承Executor接口,在Executor的基础上扩展了对Executor进行控制的方法:如提供的submit() 方法用于提交Runnable任务,invokeAll()方法用于批量提交任务,shutdown()方法用于停止启动新的任务等。

        在Java中,ExecutorService接口的实现类基于线程池来实现,这也是ExecutorService在很多场景下被简单地看成一个线程池工具的原因。常用的ExecutorService接口的实现类包括ThreadPoolExecutor和ScheduledThreadPoolExecutor。

        创建ExecutorService实例的方法如下:

  • Executors.newSingleThreadExecutor():创建一个使用单个工作线程在无界队列上运行的Executor
  • Executors.newFixedThreadPool(int nThreads):创建一个线程池,该线程池复用在共享无界队列上运行的固定数量的线程
  • Executors.newScheduledThreadPool(int corePoolSize):创建一个线程池,可以配置为在给定延迟后运行,或者定期执行

        ExecutorService接口常用的方法如下:

        void execute(Runnable command):在将来的某个时间执行给定的任务,根据实际使用的Executor的实现类,该任务可以在新线程、线程池或调用线程中执行。

        ExecutorService的使用

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

public class ExecutorServiceDemo1 {
    public static void main(String[] args) {
        // 创建一个包含了4个线程的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        MyRun1 run1 = new MyRun1(); // 创建任务对象
        for(int i =1;i<=4;i++) {
            executorService.execute(run1); // 提交4次任务
        }
        // 关闭线程池,已提交的任务继续执行,不能再提交新的任务
        executorService.shutdown();
        // executorService.execute(run1); // 抛出RejectedExecutionException
    }
}

class MyRun1 implements Runnable {
    int num = 0;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                if (num >1000){
                    break;
                }
                String name = Thread.currentThread().getName();
                System.out.println(name + ": " + num);
                num++;
            }
        }
    }
}

        ScheduledExecutorService的使用

import java.time.LocalTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecutorServiceDemo2 {
    public static void main(String[] args) {
        // 创建一个包含了4个线程的线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
        Runnable run1 = () -> {  // 任务1
            String name = Thread.currentThread().getName();
            System.out.println(name+": "+ LocalTime.now());
        };
        Runnable run2 = () -> { // 任务2
            String name = Thread.currentThread().getName();
            System.out.println("====>"+name+": "+ LocalTime.now());
        };
        // 指定延迟1秒后,每个2秒执行1次任务1
        service.scheduleAtFixedRate(run1, 1, 2, TimeUnit.SECONDS);
        // 指定延迟2秒后,每个1秒执行1次任务2
        service.scheduleAtFixedRate(run2, 2, 1, TimeUnit.SECONDS);
        // 任务1和任务2共用线程池中的4个线程
    }
}

2. 获取并发任务的结果

2.1 获取并发任务的结果概述

        前面的课程中讲述了创建线程的2种方式:一种是直接继承Thread,另外一种是实现Runnable接口。这2种方式都有一个缺陷:在任务执行完成后无法直接获取任务的执行结果。想要获取任务执行结果,必须通过共享变量或者使用线程通信的方式来达到效果,这种实现方式比较复杂。

        如上图所示:如果用多线程方式,随机生成100个10以内的数字,并求和,如何确定执行了100次,是个难题。解决办法是:用变量 count 来记载执行的次数,并不断查询变量 count 的值。这种实现方式比较麻烦,而且在解决复杂业务时,代码复杂度也会直线上升。

2.2 Callable接口

        为简化获取并发任务结果的编码, Java 5版本中增加了Callable接口和Future接口,通过它们可以在并发任务执行完毕之后便捷的得到任务执行结果。

        使用Callable接口被称为Java中创建线程的第三种方式。

        Callable接口与Runnable接口相似,代表线程的工作单元,该接口中只有一个call()方法,含义与run()方法相似。

        与run()方法不同的是,call()方法可以返回指定的泛型类对象,并且可以抛出Exception类及其子类异常。call()方法的设计为返回并发任务的结果提供了可能。

2.3 Future接口

        Future接口表示异步计算的结果,它提供了检查计算是否完成、等待其完成以及检索计算结果的方法:

  • cancel()方法:用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false
  • isCancelled()方法:表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
  • isDone()方法:表示任务是否已经完成,若任务完成,则返回true
  • get()方法:用来获取执行结果,这个方法会产生阻塞,一直等到任务执行完毕才返回执行结果
  • get(long timeout, TimeUnit unit)方法:用来获取执行结果,如果在指定时间内没能获取到结果,直接返回null
import java.util.Random;
import java.util.concurrent.*;

public class CallableDemo1 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
        MyCall cd = new MyCall();
        Future<Integer> future1 = service.submit(cd); // 提交任务
        System.out.println("isDone(): "+future1.isDone());
        try {
            int result = future1.get();
            System.out.println("get(): "+result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("isDone(): "+future1.isDone());
        // 关闭ExecutorService
        service.shutdown();
    }
}

class MyCall implements Callable<Integer> {
    Random random = new Random();
    int count =1; // 控制计算次数
    int result = 0; // 保存计算结果
    public Integer call(){
        while(true){
            synchronized (this){
                if (count>100){ // 计算100次
                    break;
                }
                int num = random.nextInt(10);
                result += num;
                String name = Thread.currentThread().getName();
                // 输出多线程执行的过程
                System.out.println(name+", num="+num+",result="+result);
                count++;
            }
            Thread.yield();
        }
        return result;
    }
}

2.4 FutureTask

        FutureTask 为 Future接口提供了基础实现,如get()方法的具体逻辑、cancel()方法的具体逻辑以及其他。

        FutureTask常用来封装Callable和Runnable,也可以作为一个任务提交到线程池中执行。

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

public class FutureTaskDemo1 {
    public static void main(String[] args) {
        int numStudents = 3;
        FutureTask<Integer>[] ftArray = new FutureTask[numStudents];
        for (int i = 0; i < numStudents; i++) {
            Student student = new Student("Student " + (i + 1));
            FutureTask<Integer> ft = new FutureTask<>(student);
            ftArray[i] = ft;
            new Thread(ft).start();
        }
        // 计算学生平均分
        int totalScore = 0;
        for (int i = 0; i < numStudents; i++) {
            try {
                totalScore += ftArray[i].get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        double averageScore = (double) totalScore / numStudents;
        System.out.println("所有学生的平均分为: " + averageScore);
    }

    static class Student implements Callable<Integer> {
        private final String name;
        public Student(String name) {
            this.name = name;
        }

        @Override
        public Integer call() throws Exception {
            // 模拟学生考试
            int score = (int) (Math.random() * 100);
            System.out.println(name + "结束考试,分数为:" + score);
            return score;
        }
    }
}

3. 总结

        1. 线程池

  • 在计算机编程中,线程池是一种软件设计模式,用于在计算机程序中实现并发执行
  • 线程池维护多个线程,等待监督程序分配任务并并发执行
  • 通过维护线程池,提高性能并避免由于频繁创建和销毁线程而导致的执行延迟

        2. ExecutorService 是一个JDK API,可以简化异步模式下运行的任务(Task)

  • 一般来说,ExecutorService 会自动提供一个线程池和一个用于为其分配任务的API

        3. Callable 接口和 Future 接口:在并发任务执行完毕之后便捷的得到任务执行结果

  • 使用Callable接口被称为Java中创建线程的第三种方式
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyan_1010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值