并发编程:线程池ExecutorService

使用线程池的好处

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

在这里插入图片描述

ThreadPoolExecutor

1. 线程池状态

  • ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

runState is stored in the high-order bits

在这里插入图片描述

状态value说明
RUNNING(当线程池创建出来的初始状态)111能接受任务,能执行阻塞任务
SHUTDOWN(调用shutdown方法)000不接受新任务,能执行阻塞任务;能执行正在执行的任务
STOP(调用shutDownNow)001不接受新任务,打断正在执行的任务,丢弃阻塞任务
TIDYING(中间状态)010任务全部执行完,活动线程也没了
TERMINATED(终结状态)011线程池终结

在这里插入图片描述
在这里插入图片描述

  • 这里使用ctlOf方法可以同时携带2种状态的值(runState和workerCount),为什么要这么做?
  • 如果要对runState和workerCount同时进行修改,需要进行2次cas,就不是原子操作了,需要加锁;现在只要一次cas操作即可修改2个变量的值,不需要加锁。
private static int ctlOf(int rs, int wc) { return rs | wc; }

2. ThreadPoolExecutor构造方法

在这里插入图片描述

1、corePoolSize--核心线程数
2、maximumPoolSize--最大线程数(应急线程数||空闲线程)
3、keepAliveTime--针对空闲线程的存活时间 如果超时了则把空闲的线程kill
4、unit--针对3的时间单位
5、workQueue--任务存放的队列
一般来说,我们应该尽量使用有界队列,如果任务数大于核心线程数,多出来的任务将在无界队列中等待,直到内存全部消耗完;
即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。
6、threadFactory--线程工厂,主要是产生线程,给线程起个自定义名字
7、handler--拒绝策略

3. 工作方式

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务;
  • 当线程数达到核心线程数上限,这时再加入任务,新加的任务会被加入队列当中去;
  • 前提是有界队列,任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程数目作为空闲线程来执行任务;
  • 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。
  • 空闲线程和核心线程在执行完之后,如果队列中有任务,这2种线程都会从队列中去获取任务,谁先抢到任务谁就执行。

4. 代码示例

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ExecutorPoolTest {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        ThreadPoolExecutor threadPoolExecutor
                = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1),
                (r) -> new Thread(r, "t" + atomicInteger.incrementAndGet()),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 3; i++) {
            threadPoolExecutor.execute(new MyTask(i));
        }
        threadPoolExecutor.shutdown();
    }

    static class MyTask implements Runnable {
        private final int taskNum;
        public MyTask(int num) {
            this.taskNum = num;
        }
        @Override
        public void run() {
            log.info("{} 正在执行task{}", Thread.currentThread().getName(), taskNum);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("task{}执行完毕============", taskNum);
        }
    }
}

在这里插入图片描述

  • 这里正常创建3个线程不会有问题,如果创建的线程数大于最大线程数+队列任务数,会报错

在这里插入图片描述

5. 超时演示

在这里插入图片描述

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 空闲线程超时的问题
 **/
@Slf4j
public class ExecutorPoolTest2 {
    static ThreadPoolExecutor threadPoolExecutor;

    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        threadPoolExecutor = new ThreadPoolExecutor(1, 2, 3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1),
                (r) -> new Thread(r, "t" + atomicInteger.incrementAndGet()),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 3; i++) {
            threadPoolExecutor.execute(new MyTask(i));
        }
    }

    static class MyTask implements Runnable {
        private final int taskNum;

        public MyTask(int num) {
            this.taskNum = num;
        }

        @Override
        public void run() {
            log.debug("{} 正在执行task{}", Thread.currentThread().getName(), taskNum);
            try {
                Thread.currentThread().sleep(3100);
                log.debug("线程数目{}", threadPoolExecutor.getPoolSize());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("task{}执行完毕============", taskNum);
        }
    }
}

在这里插入图片描述

  • 模拟空闲时间是2s

在这里插入图片描述

6. 线程数配置

1、任务数(6个)小于等于核心线程数(1个)+队列容量(5个)

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ExecutorPoolTest13 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor
                = new ThreadPoolExecutor(1, 100, 3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5));
        for (int i = 0; i < 6; i++) {
            //如果有6个线程,只会有一个线程在工作是核心线程,因为队列可以存的下多余的线程,所以不会启用空闲队列
            //如果有7个线程,会启用空闲线程,线程池里的正在工作的线程数是2
            threadPoolExecutor.execute(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //启用的线程数量
                log.info("size:{}", threadPoolExecutor.getPoolSize());
            });
        }
    }

}
  • 只有一个核心线程在工作

在这里插入图片描述
2、任务数(7个)大于核心线程数(1个)+队列容量(5个)

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ExecutorPoolTest13 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor
                = new ThreadPoolExecutor(1, 100, 3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5));
        for (int i = 0; i < 7; i++) {
            //如果有6个线程,只会有一个线程在工作是核心线程,因为队列可以存的下多余的线程,所以不会启用空闲队列
            //如果有7个线程,会启用空闲线程,线程池里的正在工作的线程数是2
            threadPoolExecutor.execute(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //启用的线程数量
                log.info("size:{}", threadPoolExecutor.getPoolSize());
            });
        }
    }

}
  • 一个核心线程和一个空闲线程在工作

在这里插入图片描述
3、核心线程数为0个

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class ExecutorPoolTest12 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor
                = new ThreadPoolExecutor(0, 100, 3, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(100000));
        for (int i = 0; i < 6; i++) {
            threadPoolExecutor.execute(()->{
                log.info("size:{}",threadPoolExecutor.getPoolSize());
            });
        }
    }

}
  • 执行这个任务的是空闲线程,而且所有线程执行完3s(设置的过期时间)后,线程池会停止工作

在这里插入图片描述

核心线程与空闲线程的区别

  1. 空闲线程可以被销毁(有过期时间)
  2. 核心线程是不会被销毁的
  • 线程池是如何保证核心线程不被销毁:核心线程拿到的第一个任务是直接分配的,调用take()(内部有await()方法)获取队列中的任务,如果队列为空,则一直阻塞当前线程;
  • 空闲线程为什么会被销毁:空闲线程拿到的第一个任务是从队列中得到的,调用poll()方法。

1、如果核心线程数不为0,先启动核心线程执行任务,如果任务大于核心线程数,放入队列,等待核心线程释放后到队列中拿任务,如果队列满了,启动空闲线程,如果又超出空闲线程就拒绝 ;
2、如果核心线程数为0,先放入队列,然后启动空闲线程执行任务。

newFixedThreadPool

1. 静态方法

  • 核心线程数 == 最大线程数(没有空闲线程被创建),因此也无需超时时间;
  • 阻塞队列LinkedBlockingQueue可以指定容量(如果不指定,数量就是Integer.MAX_VALUE);
  • 可以设置线程名称;
  • 适用于任务量已知,相对耗时的任务。

在这里插入图片描述

2. 代码示例

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ExecutorPoolTest3 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1,
                (r) -> new Thread(r, "fisher"));
        executorService.execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                log.debug("newFixedThreadPool");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        log.info("start...");
        executorService.shutdown();
    }

}

在这里插入图片描述

newCachedThreadPool

1. 静态方法

  • 核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,全部都是空闲线程60s后回收;
  • 一个可根据需要创建新线程的线程池,如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源;
  • 这种线程池比较灵活,对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。

在这里插入图片描述

2. 代码示例

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ExecutorPoolTest4 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.execute(() -> log.info("{}", Thread.currentThread().getName()));
        }
    }

}
  • 这里使用TimeUnit.SECONDS.sleep(1);然后每个线程顺序执行,而不是并发执行,使得newCachedThreadPool复用了pool-1-thread-1这个线程。

在这里插入图片描述

  • 注释掉TimeUnit.SECONDS.sleep(1);再看打印结果,10个线程都是新创建的,没有发生复用,是因为10个任务同时放入SynchronousQueue队列,pool-1-thread-1只有处理完task1之后,才能处理task2,但是task2已经在队列当中,所以要重新创建一个线程处理其他任务。

在这里插入图片描述

newSingleThreadExecutor

1. 静态方法

  • 多个任务排队执行,线程数固定为 1,任务数多于 1 时,会放入队列排队,即使任务执行完毕,这个唯一的线程也不会被释放。
  • 区别于自己创建一个单线程串行执行任务(new Thread),如果任务执行失败而终止,那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作。
  • Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改;
  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改,对外暴露的是 ThreadPoolExecutor对象,可以强转后调用 setCorePoolSize 等方法进行修改。

在这里插入图片描述

2. 代码示例

package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class ExecutorPoolTest5 {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> log.info("{}", Thread.currentThread().getName()));
        executorService.execute(() -> {
            log.info("{}", Thread.currentThread().getName());
            int i = 1 / 0;
        });
        executorService.execute(() -> log.info("{}", Thread.currentThread().getName()));
    }

}
  • pool-1-thread-1发生异常后,创建pool-1-thread-2继续执行。

在这里插入图片描述

newScheduledThreadPool

1. 静态方法

  • 创建一个线程池,可以设置线程池大小可以延时执行 、循环执行

在这里插入图片描述

2. 代码示例

  • 延迟执行,schedule()
package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ExecutorPoolTest9 {
    static AtomicInteger i = new AtomicInteger();
    static ScheduledExecutorService scheduledExecutorService;

    public static void main(String[] args) {
        log.info("main()");
        scheduledExecutorService = Executors.newScheduledThreadPool(4);
        //1.延迟2s执行
        scheduledExecutorService.schedule(() -> {
            log.info("延迟执行");
        }, 2, TimeUnit.SECONDS);
        //2.循环延迟3s执行
        excuteTask();
    }

    public static void excuteTask() {
        log.info("excuteTask,{}", i.incrementAndGet());
        scheduledExecutorService.schedule(() -> {
            excuteTask();
        }, 3, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

  • 循环执行,如果业务代码执行时间大于设置的时间间隔,则等待业务代码执行完,线程才再次执行
package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ExecutorPoolTest10 {

    public static void main(String[] args) {
        log.info("main()");
        AtomicInteger i = new AtomicInteger();
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        //按固定时间间隔循环执行,延迟3s启动线程,并每隔1s执行一遍,period包含在线程执行时长之内
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            log.info("固定时间间隔,循环执行{}", i.getAndIncrement());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 3, 1, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

  • 固定延迟时间循环执行,业务代码2s,延迟1s,则每隔3s执行一次
package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ExecutorPoolTest11 {

    public static void main(String[] args) {
        log.info("main()");
        AtomicInteger i = new AtomicInteger();
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        //按固定延迟时间循环执行,延迟3s启动线程,并每隔1s执行一遍,在线程执行完之后延迟delay时长再次执行
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            log.info("固定延迟执行{}", i.getAndIncrement());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, 3, 1, TimeUnit.SECONDS);
    }
}

在这里插入图片描述

提交任务

1. execute

在这里插入图片描述

  • 提交一个任务,没有返回值。

2. submit

在这里插入图片描述

  • 提交一个任务有返回值;
  • 使用get()方法获取返回值,方法会阻塞,只有等待线程执行完,才能获取。
  • 代码示例
package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

@Slf4j
public class ExecutorPoolTest6 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> submit = executorService.submit(() -> {
            log.info("{}", Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(1);
            return "success!";
        });
        log.info("start");
        log.info(submit.get());
        log.info("end");
    }

}

在这里插入图片描述

3. invokeAll

在这里插入图片描述

  • 提交所有的任务
  • 代码示例
package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j
public class ExecutorPoolTest7 {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        List<Future<String>> futures = executorService.invokeAll(Arrays.asList(
                () -> {
                    log.info("1");
                    return "1";
                },
                () -> {
                    log.info("2");
                    return "2";
                },
                () -> {
                    log.info("3");
                    return "3";
                },
                () -> {
                    log.info("4");
                    return "4";
                }
        ));
        log.info("start");
        futures.forEach(f -> {
            try {
                log.info(f.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        log.info("end");
    }

}

在这里插入图片描述

4. invokeAny

在这里插入图片描述

  • 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
package org.example.ThreadPool;

import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;

@Slf4j
public class ExecutorPoolTest8 {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        String str= executorService.invokeAny(Arrays.asList(
                () -> {
                    TimeUnit.MILLISECONDS.sleep(3000);
                    log.info("1");
                    return "1";
                },
                () -> {
                    TimeUnit.MILLISECONDS.sleep(2000);
                    log.info("2");
                    return "2";
                },
                () -> {
                    TimeUnit.MILLISECONDS.sleep(1000);
                    log.info("3");
                    return "3";
                },
                () -> {
                    TimeUnit.MILLISECONDS.sleep(4000);
                    log.info("4");
                    return "4";
                }
        ));
        log.info("start");
        log.info(str);
        log.info("end");
    }

}

在这里插入图片描述

停止任务

1. shutdown

线程池状态变为SHUTDOWN
不会接收新任务
但已提交任务会执行完
不会阻塞调用线程的执行
void shutdown();

2. shutdownNow

线程池状态变为 STOP
不会接收新任务
会将队列中的任务返回
并用 interrupt 的方式中断正在执行的任务
List< Runnable> 中保存了队列中排队的任务
List< Runnable> shutdownNow();

3. awaitTermination

调用 shutdown后,调用线程并不会等待所有任务运行结束,可以利用此方法等待
等待线程池成为终结状态执行,但是会提前(线程池内的线程执行时间小于timeout),或超时(线程池内的线程执行时间大于timeout),为了在线程池结束之后,做善后工作(比如释放资源)
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

合理地配置线程池

从以下几个角度来分析:

  • 任务的性质:CPU 密集型任务、IO 密集型任务和混合型任务。
  • 任务的优先级:高、中和低。
  • 任务的执行时间:长、中和短。
  • 任务的依赖性:是否依赖其他系统资源,如数据库连接。

CPU 密集型任务应配置尽可能小的线程,如配置 Ncpu+1 个线程的线程池。 由于 IO 密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu。(可以通过 Runtime.getRuntime().availableProcessors()方法获得当前设备的 CPU 个数)

优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可 以让优先级高的任务先执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先 级队列,让执行时间短的任务先执行。

依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果, 等待的时间越长,则 CPU 空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用 CPU。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值