多线程(一)常见线程池与阻塞队列

94 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

线程池

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

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

创建方法

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

image.png 使用ThreadPoolExecutor创建线程,

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

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

image.png 参数解释

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

拒绝策略

image.png jdk一共提供了四种拒绝策略:
abort 抛异常
discard 扔掉,不抛异常
discardOldest 扔掉排队时间最久的
callerRuns 调用者处理任务,谁调用谁来处理新来的任务

常见的线程池

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

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

image.png

newFixedThreadPool

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

image.png LinkedBlockingQueue最大大小为Integer.MAX_VALUE

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

image.png

newCachedThreadPool

image.png

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

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

newSingleThreadExecutor

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

newScheduledThreadPool

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

newWorkStealingPool

image.png 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.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();
    }
}

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