线程池你真不来了解一下吗?(一)

简介: 笔记

本篇主要是讲解线程池,这是我在多线程的倒数第二篇了,后面还会有一篇死锁。主要将多线程的基础过一遍,以后有机会再继续深入

那么接下来就开始吧,如果文章有错误的地方请大家多多包涵,不吝在评论区指正哦~

声明:本文使用JDK1.8


一、线程池简介


线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用

我们来看看如果没有使用线程池的情况是这样的:

  • 为每个请求都新开一个线程
public class ThreadPerTaskWebServer {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(80);
        while (true) {
            // 为每个请求都创建一个新的线程
            final Socket connection = socket.accept();
            Runnable task = () -> handleRequest(connection);
            new Thread(task).start();
        }
    }
    private static void handleRequest(Socket connection) {
        // request-handling logic here
    }
}

为每个请求都开一个新的线程虽然理论上是可以的,但是会有缺点

  • 线程生命周期的开销非常高。每个线程都有自己的生命周期,创建和销毁线程所花费的时间和资源可能比处理客户端的任务花费的时间和资源更多,并且还会有某些空闲线程也会占用资源
  • 程序的稳定性和健壮性会下降,每个请求开一个线程。如果受到了恶意攻击或者请求过多(内存不足),程序很容易就奔溃掉了。

所以说:我们的线程最好是交由线程池来管理,这样可以减少对线程生命周期的管理,一定程度上提高性能。


二、JDK提供的线程池API


JDK给我们提供了Excutor框架来使用线程池,它是线程池的基础

  • Executor提供了一种将“任务提交”与“任务执行”分离开来的机制(解耦)

下面我们来看看JDK线程池的总体api架构:

1.jpg

接下来我们把这些API都过一遍看看:

Executor接口:

2.jpg

ExcutorService接口:

3.jpg

AbstractExecutorService类:

4.jpg

ScheduledExecutorService接口:

5.jpg

ThreadPoolExecutor类:

6.jpg

ScheduledThreadPoolExecutor类:

7.jpg


2.1ForkJoinPool线程池


除了ScheduledThreadPoolExecutor和ThreadPoolExecutor类线程池以外,还有一个是JDK1.7新增的线程池:ForkJoinPool线程池

于是我们的类图就可以变得完整一些:

8.jpg

JDK1.7中新增的一个线程池,与ThreadPoolExecutor一样,同样继承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的两大核心类之一。与其它类型的ExecutorService相比,其主要的不同在于采用了工作窃取算法(work-stealing):所有池中线程会尝试找到并执行已被提交到池中的或由其他线程创建的任务。这样很少有线程会处于空闲状态,非常高效。这使得能够有效地处理以下情景:大多数由任务产生大量子任务的情况;从外部客户端大量提交小任务到池中的情况。

来源:


2.2补充:Callable和Future


学到了线程池,我们可以很容易地发现:很多的API都有Callable和Future这么两个东西。

Future<?> submit(Runnable task)
    <T> Future<T> submit(Callable<T> task)

其实它们也不是什么高深的东西~~~

我们可以简单认为:Callable就是Runnable的扩展

  • Runnable没有返回值,不能抛出受检查的异常,而Callable可以

9.png

也就是说:当我们的任务需要返回值的时,我们就可以使用Callable!

Future一般我们认为是Callable的返回值,但他其实代表的是任务的生命周期(当然了,它是能获取得到Callable的返回值的)

10.jpg

简单来看一下他们的用法:

public class CallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);
        // 可以执行Runnable对象或者Callable对象代表的线程
        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));
        // V get()
        Integer i1 = f1.get();
        Integer i2 = f2.get();
        System.out.println(i1);
        System.out.println(i2);
        // 结束
        pool.shutdown();
    }
}

Callable任务:

public class MyCallable implements Callable<Integer> {
    private int number;
    public MyCallable(int number) {
        this.number = number;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }
}

执行完任务之后可以获取得到任务返回的数据

11.png

目录
相关文章
|
10月前
|
缓存 Java 应用服务中间件
线程池的10个坑你都遇到过吗
日常开发中,为了更好管理线程资源,减少创建线程和销毁线程的资源损耗,我们会使用线程池来执行一些异步任务。但是线程池使用不当,就可能会引发生产事故。大家看完肯定会有帮助的~
147 0
|
2天前
|
存储 Java 调度
浅谈线程池
浅谈线程池
15 1
|
10月前
|
缓存 Java
常用的线程池有哪些?
常用的线程池有哪些?
78 0
|
10月前
|
存储 Java 调度
线程池使用
线程池使用
|
11月前
|
缓存 算法 Java
线程池和使用
线程池是一种用于管理和复用线程的机制。在多线程应用程序中,线程的创建和销毁需要消耗大量的系统资源,而线程池可以通过预先创建一定数量的线程,然后将任务分配给这些线程来避免频繁地创建和销毁线程,从而提高应用程序的性能和效率。线程池还可以控制并发线程的数量,避免过多的线程竞争资源导致的性能下降和系统崩溃。线程池是多线程编程中常用的一种技术,被广泛应用于各种类型的应用程序中。
55 0
线程池和使用
|
Java 调度
线程池 的一些事
线程池 的一些事
100 0
线程池 的一些事
|
Java
线程池的实现
线程池的实现
65 0
线程池的实现
|
监控 Java
关于线程池,你需要了解这些
1、 降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。 2、 提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。 3、 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
111 0
|
存储 监控 Java
线程池
线程池
106 0