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

简介: 笔记

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

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

声明:本文使用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

目录
打赏
0
0
0
0
1281
分享
相关文章
6. 实现简单的线程池
6. 实现简单的线程池
66 0
线程池:我是谁?我在哪儿?
大家好,这篇文章跟大家探讨下日常使用线程池的各种姿势,重点介绍怎么在 Spring 环境中正确使用线程池。
330 1
线程池:我是谁?我在哪儿?

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等