文章目录

  • 前言
  • 1. 为什么要使用线程池?
  • 2. 创建线程池
  • 3. 线程池工作流程
  • 4. Runnable和Callable
  • 4. 正确使用线程池
  • 4.1 避免使用无界队列
  • 4.2 选择合适的拒绝策略
  • 4.3 处理异常
  • 4.4 获取结果
  • 项目推荐


Java线程池使用

前言

创建定长线程池事例:

ExecutorService fixedThreadPool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<Runnable>(),
                new ThreadFactory() {
                    private final AtomicInteger threadNum = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("thread-" + threadNum.getAndIncrement());
                        // 捕获运行时异常
                        thread.setUncaughtExceptionHandler((t, e) -> {
                            System.out.println("线程" + thread.getName() + " 运行出错:" + e);
                        });

                        return thread;
                    }
                });

1. 为什么要使用线程池?

  1. 降低创建/销毁线程的系统开销
  2. 资源限制和管理,防止线程并发数量过多,抢占系统资源从而导致阻塞

2. 创建线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  • corePoolSize 核心线程数
  • maximumPoolSize 最大线程数
  • keepAliveTime 空闲线程存活时间
  • unit 空闲线程存活时间的单位
  • workQueue 工作队列
  • threadFactory 新线程属性设置
  • handler 拒绝策略

handler拒绝策略
线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是运行时异常,容易忘记捕获。如果不关心任务被拒绝的事件,可以设置成DiscardPolicy

拒绝策略

拒绝行为

AbortPolicy

抛出RejectedExecutionException

DiscardPolicy

什么也不做,直接忽略

DiscardOldestPolicy

丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置

CallerRunsPolicy

CallerRunsPolicy

缓冲队列有类型

  1. 直接提交SynchronousQueue,是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
  2. 无界队列LinkedBlockingQueue,当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。
  3. 有界队列ArrayBlockingQueue,可以指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。

常用线程池类型

  1. newCachedThreadPool:缓冲线程池,内部使用SynchronousQueue
  2. newFixedThreadPool:单线程
  3. newScheduledThreadPool:内部使用DelayedWorkQueue
  4. newSingleThreadExecutor
    使用

比较重要的几个
类:

java接口 使用 线程池 java线程池使用实例_ide

3. 线程池工作流程

  1. 如果运行的线程小于corePoolSize,则添加新线程来执行。
  2. 如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
  3. 如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略。

corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略

4. Runnable和Callable

区别如下:

  1. 方法签名不同,void Runnable.run(), V Callable.call() throws Exception
  2. 是否允许有返回值,Callable允许有返回值
  3. 是否允许抛出异常,Callable允许抛出异常。

三种提交任务的方式:

提交方式

是否关心返回结果

Future submit(Callable task)


void execute(Runnable command)


Future<?> submit(Runnable task)

否,虽然返回Future,但是其get()方法总是返回null

4. 正确使用线程池

4.1 避免使用无界队列

不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为这种方式会使用无界的任务队列,容易OOM,我们应该使用ThreadPoolExecutor的构造方法手动指定队列的最大长度:

ExecutorService executorService = new ThreadPoolExecutor(10, 10, 
                0, TimeUnit.SECONDS, 
                new ArrayBlockingQueue<>(1000), 
                new ThreadPoolExecutor.DiscardPolicy());

4.2 选择合适的拒绝策略

线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是运行时异常,容易忘记捕获。

4.3 处理异常

Future异常
调用Future.get()方法时获取,执行过程中的异常会被包装成ExecutionException,submit()方法本身不会传递结果和任务执行过程中的异常

ExecutorService executorService = Executors.newFixedThreadPool(4);
        Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                // 异常会在调用Future.get()时传递给调用者
                throw new RuntimeException("exception is me");
            }
        });

        try {
            Object result = future.get();
        } catch (InterruptedException e) {
            // interrupt
        } catch (ExecutionException e) {
            // exception in Callable.call()
            e.printStackTrace();
        }

excute捕获运行时异常
多线程环境中,主线程通过try catch无法捕获子线程异常。
解决办法使用UncaughtExceptionHandler或者在子线程中try,catch处理。

ExecutorService fixedThreadPool = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<Runnable>(),
                new ThreadFactory() {
                    private final AtomicInteger threadNum = new AtomicInteger(1);

                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("thread-" + threadNum.getAndIncrement());
                        // 捕获异常
                        thread.setUncaughtExceptionHandler((t, e) -> {
                            System.out.println("线程" + thread.getName() + " 运行出错:" + e);
                        });

                        return thread;
                    }
                });

        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
//                try{
                    System.out.println(1/0);
//                }catch (Exception e){
//                    System.out.println("asdasdasd");
//                }
            }
        });

以上代码运行结果:

线程thread-1 运行出错:java.lang.ArithmeticException: / by zero

4.4 获取结果

获取单个结果
过submit()向线程池提交任务后会返回一个Future,调用V Future.get()方法能够阻塞等待执行结果,V get(long timeout, TimeUnit unit)方法可以指定等待的超时时间。

获取多个结果
使用ExecutorCompletionService,该类的take()方法总是阻塞等待某一个任务完成,然后返回该任务的Future对象。向CompletionService批量提交任务后,只需调用相同次数的CompletionService.take()方法,就能获取所有任务的执行结果,获取顺序是任意的,取决于任务的完成顺序

事例:

Executor executor = Executors.newFixedThreadPool(3);
        CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(executor);
        for (int i = 0; i < 10; i++) {
            cs.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    Random r = new Random();
                    int init = 0;
                    for (int i = 0; i < 100; i++) {
                        init += r.nextInt();
                        Thread.sleep(100);
                    }
                    return Integer.valueOf(init);
                }
            });
        }
        for (int i = 0; i < 10; i++) {
            try {
                Future<Integer> future = cs.take();
                if (future != null) {
                    System.out.println(future.get());
                }
            } catch (Exception e) {
                System.out.println("error");
            }

        }