线程池, 顾名思义, 放线程的池子嘛, 这个池子可以存放多少线程取决于你自己采用什么样的线程池, 你的硬件资源, 以及并发线程的数量. JDK 提供了下面的四种线程池:
固定线程数的线程池
最简单的
在 Java 中创建一个线程池, 这很简单, 只需要两行代码.
- ExecutorService executor = Executor.newFixedTreadPool(6);// 固定线程是 6
- // 线程一般设置成 processor 核心数的倍数, 因为我这台机器是 6 核的, 所以设成 6. 这也是充分利用硬件嘛
- // 执行线程任务
- executor.execute(new Runnable(){
- @Override
- public void run(){
- //do nothing
- }
- })
- executor.shutdown();
Executor 是 Java 并发包中提供的, 用来创造不同类型的线程池.
Attention
但是在多人合作或者是一些部署上线的项目里, 是不允许去使用这种方法的, 因为它是有性能隐患的.
Executors 在创建线程池的时候, 用的是 new LinkedBlockingQueue(), 它这个队列本身是无边界的, 但是线程是固定数量的. 这就意味着, 在程序运行的过程中, 最多会有 N 个线程在处于活动状态. 每次有新的任务来就会等待, 直到有线程处于空闲状态. 所有的线程都会处于线程池里里面, 直到 shutdown() 的执行.
它的问题就在于来者不拒, 只要有任务来, 你就进队列等着. 在入队列和出队列用的并不是同一个 lock, 在多 processor 的机器上, 是可以做到真正意义上的并行的. 拿经典的生产者和消费者来举例子, 在同一个时间点, 有的在消费, 有的在生产.
这种线程池不会销毁线程, 不会拒绝任务, 固定线程数. 所以如果不停的加入任务, 会导致很糟糕的内存占用, 老年代可能会被占满.
稍复杂的 (可以延时执行, 也可以执行带返回值的任务)
- public static void main(String[] args) throws InterruptedException, ExecutionException {
- TestThread testThread = new TestThread();
- System.out.println(testThread.processors);
- ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(6);
- FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() {
- @Override
- public String call() throws Exception {
- return Thread.currentThread().getName();
- }
- });
- scheduledExecutorService.submit(futureTask);
- // 获取返回值
- String result = futureTask.get();
- System.out.println("result :"+result);
- // 执行延时任务
- scheduledExecutorService.schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName()+": bomb!");
- }
- },3L,TimeUnit.SECONDS);
- }
- Output:
- result :pool-1-thread-1
- pool-1-thread-1: bomb!
缓存的线程池
核心池大小为 0, 线程池最大线程数目为最大整型, 这意味着所有的任务一提交就会 wait. 当线程池中的线程有 60s 没有执行任务就会被 Kill, 阻塞队列为 SynchronousQueue.SynchronousQueue 的 take 操作需要 put 操作等待, put 操作需要 take 操作等待, 否则会阻塞 (线程池的阻塞队列不能存储, 所以当目前线程处理忙碌状态时, 会开辟新的线程来处理请求 **), 线程进入 wait set.
总结一下这是一个可以无限扩大的线程池; 适合处理执行时间比较小的任务; 线程空闲时间超过 60s 就会被 Kill, 所以长时间处于空闲状态的时候, 这种线程池几乎不占用资源, 因为它压根没有线程在里面; 阻塞队列没有存储空间, 只要请求到来, 就必须找到一条空闲线程去处理这个请求, 找不到则在线程池新开辟一条线程.
如果主线程提交任务的速度远远大于 CachedThreadPool 的处理速度, 则 CachedThreadPool 会不断地创建新线程来执行任务, 这样有可能会导致系统耗尽 CPU 和内存资源, 所以在使用该线程池时, 要注意控制并发的任务数. 如果是一个不断增长的任务需求, 很容易就会到性能瓶颈, 它会不停的创建新的线程.
- ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- for (int i = 0; i < 10; i++) {
- cachedThreadPool.execute(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName());
- }
- });
- }
- cachedThreadPool.shutdown();
- Output:
- pool-1-thread-2
- pool-1-thread-6
- pool-1-thread-1
- pool-1-thread-7
- pool-1-thread-8
- pool-1-thread-3
- pool-1-thread-5
- pool-1-thread-9
- pool-1-thread-4
- pool-1-thread-10
单个线程的线程池
SingleThreadExecutor 是使用单个 worker 线程的 Executor. 只有一种情况会有新的线程加入线程池, 那就是原有的线程运行时有抛出异常, 这时就会有创建的新的线程来替代它的工作.
拿生产者消费者模型来说的话, 这就是一个单一消费者的模型.
(ps. 一般可以用来做一些日志记录
- public static void main(String[] args) {
- // 永远是一条线程
- ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
- for (int i = 0; i < 10; i++) {
- final int j = i;
- singleThreadPool.execute(new Runnable() {
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + ":" + j);
- }
- });
- }
- singleThreadPool.shutdown();
- }
- Output:
- pool-1-thread-1:0
- pool-1-thread-1:1
- pool-1-thread-1:2
- pool-1-thread-1:3
- pool-1-thread-1:4
- pool-1-thread-1:5
- pool-1-thread-1:6
- pool-1-thread-1:7
- pool-1-thread-1:8
- pool-1-thread-1:9
来源: https://www.cnblogs.com/QuixoteY/p/11243997.html