Java线程池内容记录

一、 线程池
实现了对线程的复用,统一管理和维护线程,减少没有必要的开销。
为什么要用线程池?
为了提高效率,需要将一些业务采用多线程的方式去执行。几乎所有需要异步或并发执行任务的程序都可以使用线程池。
线程池的概念和连接池是类似的。在Java集合中存储大量的线程对象,每次执行异步操作或者多线程操作时,直接从集合中拿到线程对象执行方法,不需要每次创建和销毁线程。

二、 Jdk自带的线程池:
Jdk中基于Executors工具类提供了很多种线程池,一般不会用。

2.1 newFixedThreadPool
这个线程池的线程数的固定的,构建线程池的时候指定。线程是懒加载,当线程池创建好后,线程池中是没有线程的,是随着任务的提交将线程在线程池中创建出来。如果任务过来,没有空闲线程了,会把任务放在阻塞队列里(LinkedBlockingQueue无界队列)。直到有空闲线程,则从阻塞队列里获取一个任务进行处理。

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        threadPool.execute(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-thread1");
        });

        threadPool.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "-thread2");
        });
    }

如果是局部变量在方法中新创建的线程池,在使用完毕之后要执行shutdown()方法,避免线程无法结束。
如果是全局的线程池,很多业务都会到,使用完成后不要shutdown,因为其他业务也要使用这个线程池。

2.2 newSingleThreadExecutor
单例线程池,线程池中只有一个工作线程在处理任务,如果任务过来没有空闲线程,则进入阻塞队列中排队等待。当前
如果业务需要顺序处理,则可以采用newSingleThreadExecutor这种线程池。
2.3 newCachedThreadPool
是一个可缓存的线程池。当第一次提交任务时,线程池中没有线程,会直接新建一个工作线程,这个线程执行完后,如果60秒内没有任务可以执行,会销毁这个线程。如果在等待60秒期间有任务进来,则再次拿到这个线程使用。如果后续提交任务时,没有空闲线程时,则新创建线程去执行。
这个线程池的最大线程数是Integer.MAX_VALUE,代表了只有有任务过来,就会有线程去执行处理,如果线程数量太多,会消耗很多的资源。实际中应避免使用这种方式创建线程池。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

2.4 newScheduleThreadPool
定时任务的线程池,这个线程池就是能以一定周期去执行一个任务,或者是延迟多久执行一个任务一次

是在ThreadPoolExecutor线程池的基础上实现了定时任务的功能,原理是基于DelayQueue实现的延迟执行。周期性执行是任务执行完毕后,再次扔回到阻塞队列。

public class Executors {
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
}

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
}
public class ThreadPoolExecutor extends AbstractExecutorService {
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
}

2.5 newWorkStealingPool
工作窃取线程池
这个线程池是基于ForkJoinPool构建的线程池。任务过来
在ThreadPoolExecutor中只有一个阻塞队列存放当前任务,一个任务只能由一个线程来完成。
ForkJoin可以将一个大任务拆分成多个小任务,放到线程的阻塞队列中。让其他的空闲线程去处理有任务的线程的阻塞队列中的任务。这种线程池中一个线程有一个阻塞队列。
核心思想:工作窃取/分而治之,希望没有工作线程处于空闲状态,让每个线程使用率更多。

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
  • 怎么拆分呢?
  • 使用场景:

三、自定义线程池的使用
在实际使用中,一般是基于ThreadPoolExecutor新建一个自定义线程池来用的,他提供了7个参数。可以手动设置线程池的核心属性,以配置的方式控制,也利于按照实际使用情况来修改。
自定义线程池,可以细粒度的控制线程池,管理内部的属性,设置Thread信息,帮助后期排查问题。
因为使用Jdk提供的几种创建线程的方式,可以自定义的参数少,最多有2个(核心工作线程数+threadFactory),对线程池的控制力度粗。

public ThreadPoolExecutor(
    int corePoolSize,           // 核心工作线程(当前任务执行结束后,不会被销毁)
    int maximumPoolSize,        // 最大工作线程
    long keepAliveTime,         // 非核心工作线程在阻塞队列位置等待的时间
    TimeUnit unit,              // 非核心工作线程在阻塞队列位置等待时间的单位
    BlockingQueue<Runnable> workQueue,   // 任务在没有核心工作线程处理时,任务先放到阻塞队列中
    ThreadFactory threadFactory,         // 构建线程的线程工厂,可以设置thread的一些信息
    RejectedExecutionHandler handler) {  // 当线程池无法处理投递过来的任务时,执行当前的拒绝策略
}

示例demo:

package com.example.mpdemo.thread;

import java.util.concurrent.*;

public class ThreadTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int corePoolSize = 2;
        int maximumPoolSize = 5;
        long keepAliveTime = 10;
        ThreadFactory threadFactory = new ThreadFactory() {

            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("thread-test");
                return thread;
            }
        };

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                threadFactory,
                new MyRejectedExecution());
        // 处理没有返回结果的任务
        threadPoolExecutor.execute(() -> {
            System.out.println("thread-execute");

        });
        // 处理有返回结果的任务,也可以处理没有返回结果的任务
        Future<Object> future = threadPoolExecutor.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                System.out.println("thread-submit");

                return "success";
            }
        });
        Object result = future.get();
        threadPoolExecutor.shutdown();

    }

    private static class MyRejectedExecution implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("拒绝策略测试");
        }
    }

}

四、Jdk提供的拒绝策略:

  1. AbortPolicy:当前拒绝策略在无法处理任务时,直接抛出异常。
    public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
  1. CallerRunsPolicy:当前拒绝策略在无法处理任务时,将任务交给调用者来处理
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
  1. DiscardPolicy:当前策略在线程池无法处理任务时,将任务丢掉
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
  1. DiscardOldestPolicy:当前策略在线程池无法处理任务时,将队列中最早的任务丢掉,将当前任务再次尝试交给线程池处理
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
  1. 自定义策略,按照业务需求自己处理实现拒绝策略
    步骤:实现RejectedExecutionHandler接口,重写rejectedExecution

五、自定义线程池的核心参数设置?
一般有3个主要的参数

  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大线程数
  3. workQueue:工作队列

关与参数的理解简单描述:

有新任务时,如果线程池的工作线程数量<核心线程数,就会创建新的线程,直到达到核心线程数量。新任务过来时,核心线程数满了,则把任务先放到阻塞队列里,由线程池来调度。如果阻塞队列满了且正在运行的线程数量小于最大线程数,则创建非核心线程执行这个任务。如果达到可以创建的最大线程数,且阻塞队列已满,则执行拒绝策略。

我们任务有CPU密集型,和IO密集型,有代表的公式。一般建议是将核心线程池设置为CPU核心数 + 1,最大线程池大小设置为CPU核心数 x 2
实际上用的时候,自定义线程池时候,使用配置设置参数,然后压测得到一个合理的数值,配置在实际项目中。

posted @   二十四桥冷月夜  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示