线程池

在阿里巴巴的开发规约当中,有这么一条

拆解线程池_java

为了防止创建销毁线程切换带来的开销,我们需要尽量使用线程池。

创建方法

在代码当中,有这么些个类

拆解线程池_java_02

使用ThreadPoolExecutor创建线程,

ExecutorService service = new ThreadPoolExecutor(‘七个参数’);

我们debug进入ThreadPoolExecutor类当中,可以看到

拆解线程池_java_03

参数解释

corePoolSize : 核心线程数,这部分线程就算没有执行也不会归还给操作系统 maximumPoolSize:最大线程数 keepAliveTime:存活时间,普通线程多长时间没有执行就会归还给操作系统 unit:keepAliveTime 单位 workQueue : 工作队列,装任务的BlockingQueue threadFactory : 线程工厂,可以通过自定义线程工厂,来设置线程的名字,方便出问题排查 handler: 拒绝策略,如果队列满了,所有线程都忙着,这个时候再来新的任务,就执行拒绝策略。

拒绝策略

拆解线程池_java_04

jdk一共提供了四种拒绝策略:

abort 抛异常

discard 扔掉,不抛异常

discardOldest 扔掉排队时间最久的

callerRuns 调用者处理任务,谁调用谁来处理新来的任务

常见的线程池

在java当中可以使用Executors来创建常见的线程池

ExecutorService service = Executors.newFixedThreadPool(参数...)

拆解线程池_java_05

newFixedThreadPool

拆解线程池_java_06

如图,newFixedThreadPool 是一种 核心线程数和最大线程数一致 的线程池。BlockingQueue使用的是 LinkedBlockingQueue

拆解线程池_java_07

LinkedBlockingQueue最大大小为Integer.MAX_VALUE

拆解线程池_java_08

我们点开 ThreadPoolExecutor,可以看到工厂使用的是默认的线程工厂,拒绝策略使用的是 AbortPolicy,即队列满后来新的任务抛异常

拆解线程池_线程池_09

newCachedThreadPool

拆解线程池_线程池_10

newCachedThreadPool 的特点:
核心线程数为0,
最大线程数为Integer.MAX_VALUE,
工作队列为 SynchronousQueue,SynchronousQueue是一种大小为0的队列。
在线程工厂和拒绝策略上面和newFixedThreadPool一样

即,newCachedThreadPool 的工作模式为:有线程来就执行,有空闲的线程由空闲的线程执行,没有空闲的线程则起新线程来执行

newSingleThreadExecutor

拆解线程池_java_11

单线程的线程池,核心线程数和最大线程数都是1,其余的队列,工厂,执行策略可等和newFixedThreadPool一样。那么问题来了,为啥我不能单独创建一个线程而用线程池呢,个人感觉有几个有点 1、可以保证扔进去的任务的执行顺序 2、线程池可以帮助管理生命周期

newScheduledThreadPool

拆解线程池_线程池_12

newScheduledThreadPool 的特殊之处在于,它的阻塞队列使用的是DelayedWorkQueue 是一个定时的阻塞队列,可以设定间隔多久执行。因此这个线程池是一个可以用于定时任务的线程池。 然而,这个线程池用的很少,面对复杂的定时任务,quartz是个更好的选择。

newWorkStealingPool

拆解线程池_java_13

newWorkStealingPool 是一个ForkJoinPool。主要执行策略为 work stealing方法。每个线程维护自己的队列,自己队列任务执行完成了可以去拿别的线程未执行的任务。ForkJoinPool 有两个执行任务 执行任务RecursiveTask:有返回值 RecursiveAction:无返回值不带返回值的计算

import java.util.concurrent.RecursiveAction;

public class PrintTask extends RecursiveAction {

    private static final int THRESHOLD = 9;

    private  int start;

    private  int end;

    public PrintTask(int start, int end) {
        super();
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {

        if(end - start  < THRESHOLD) {
            for(int i=start;i<=end;i++) {
                System.out.println(Thread.currentThread().getName()+",i="+i);
            }
        }else {
            int middle = (start + end) / 2;
            PrintTask firstTask = new PrintTask(start,middle);
            PrintTask secondTask = new PrintTask(middle+1,end);
            invokeAll(firstTask,secondTask);
        }

    }
}

主方法

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.TimeUnit;

public class ForkJoinPoolTest {

    public static void main(String[] args) throws Exception{
        testNoResultTask();
    }

    private static void testNoResultTask() throws InterruptedException{
        ForkJoinPool pool = new ForkJoinPool();
        pool.submit(new PrintTask(1,50));
        pool.awaitTermination(2, TimeUnit.SECONDS);
        pool.shutdown();
    }
}

执行时能够将1-50并行的print出来。带返回值的计算

import java.util.concurrent.RecursiveTask;

public class CalculateTask extends RecursiveTask<Integer> {
    private static final int THRESHOLD = 49;
    private int start;
    private int end;

    public CalculateTask(int start, int end) {
        this.start = start;
        this.end = end;
    }


    @Override
    protected Integer compute() {
        if (end - start <= THRESHOLD) {
            int result = 0;
            for (int i = start; i <= end; i++) {
                result += i;
            }
            return result;
        } else {
            int middle = (start + end) / 2;
            CalculateTask firstTask = new CalculateTask(start, middle);
            CalculateTask secondTask = new CalculateTask(middle + 1, end);
            invokeAll(firstTask,secondTask);
            return firstTask.join() + secondTask.join();
        }
    }

}

主函数如下

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.TimeUnit;

public class ForkJoinPoolTest {

    public static void main(String[] args) throws Exception{
        testHasResultTask();
    }
    
    public static void testHasResultTask() throws Exception {
        int result1 = 0;
        for (int i = 1; i <= 1000000; i++) {
            result1 += i;
        }
        System.out.println("循环计算 1-1000000 累加值:" + result1);

        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Integer> task = pool.submit(new CalculateTask(1, 1000000));
        int result2 = task.get();
        System.out.println("并行计算 1-1000000 累加值:" + result2);
        pool.awaitTermination(2, TimeUnit.SECONDS);
        pool.shutdown();
    }
}

具体的大家可以去网上搜索看看。

自定义工厂

拆解线程池_线程池_14

参考一下这边的DefaultThreadFactory,我们可以直接改一改线程的名字写出我们自己的ThreadFactory 举个例子:

public class MyThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    MyThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
         //此处只修改了一下名字前缀      
        namePrefix = "业务1-" +
                poolNumber.getAndIncrement() +
                "-thread-";
        System.out.println("--->"+namePrefix);
    }
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                namePrefix + threadNumber.getAndIncrement(),
                0);
        if (t.isDaemon()){
            t.setDaemon(false);
        }
        if (t.getPriority() != Thread.NORM_PRIORITY){
            t.setPriority(Thread.NORM_PRIORITY);
        }
        return t;
    }
}

主方法当中

ExecutorService service = new ThreadPoolExecutor(3, 3, 10, TimeUnit.SECONDS,new SynchronousQueue<>(),new MyThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
       service.execute(()->{
            System.out.println("popl "+Thread.currentThread().getName());
        });
        service.execute(()->{
            System.out.println("pop2 "+Thread.currentThread().getName());
        });

输出结果如图,经过规范的定义之后,线程的名字就可以辨认出来,以后也更好排查

--->业务11-thread-
popl 业务11-thread-1
pop2 业务11-thread-2

自定义拒绝策略

public class MyHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("这个是我的策略");
    }
}

同样,我们只需要实现 RejectedExecutionHandler 接口,并且实现当中的方法,方法当中就是我们的拒绝策略。主方法:

ExecutorService service = new ThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS,new SynchronousQueue<>(),new MyThreadFactory(), new MyHandler());
        service.execute(()->{
            System.out.println("popl "+Thread.currentThread().getName());
        });
        service.execute(()->{
            System.out.println("pop2 "+Thread.currentThread().getName());
        });

此处我将自定义的线程池线程调为1,队列调为大小为0 的队列,拒绝策略改为自定义的拒绝策略。在来了第二个任务之后,拒绝策略触发。执行结果

--->业务11-thread-
popl 业务11-thread-1
这个是我的策略