线程池状态

ThreadPoolExecutor内部由一个32位的字段ctl来标识线程池的状态。其中前三位代表线程池的状态,后29位代表了线程池的数量。

线程池的状态共划分为RUNNING(111),SHUTDOWN(000),STOP(001),TIDYING(010),TERMINATED(011)。

默认情况下,TERMINATED>TUDYING>STOP>SHUTDOWN>RUNNING状态。第一位其实是正负号。

RUNNING代表了线程池正在运行。

SHUTDOWN代表了线程池不会接收新任务,但会将阻塞队列中的任务执行完成。

STOP代表线程池不会执行新任务,并且阻塞队列中的任务也要抛弃掉。

TIDYING代表了当前线程池任务全部执行完成,活动线程为0,线程池准备结束

TERMINATED代表当前线程池已经消亡。

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_阻塞队列

之所以要这么设计,将线程池状态与线程池数量拼接到一块,目的是为了可以使用CAS对结果一次性完成更改,保护数据安全。

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_学习_02

线程池的构造方法

线程池的构造方法有很多,最底层的是七个结果的这种:

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_阻塞队列_03

 corePoolSize代表了核心线程数,最多保留的线程数。

maximumPoolSize,代表当前线程池的最大线程数,corePoolSize+救急线程的数量

keepaliveTime,代表了当前线程池中,救急线程的存活时间。

unit,救急线程存活的时间单位。

workQueue,代表了阻塞队列,就是当核心线程数足够后放入至阻塞队列中。

ThreadFactory,线程工厂,用来创建线程的工厂。

handler,任务拒绝策略,默认情况下抛一个异常。

可以看一下调用了该构造的其它构造方法,也就是四类线程池的构造方法,后续介绍。先说一下流程。

1.当线程池创建对象时,会首先校验corePoolSize是否满了,如果没有满,则创建新的线程来执行任务。

2.如果corePoolSize满了,判断workQueue是否满了,如果没有满,那么会将当前任务存放至workQueue中。

3.如果workQueue都满了,那么会创建救急线程,最大数量为maximumSize-corePoolSize的数量,如果救急线程也满了,那么只能调用任务拒绝策略来对结果进行结束了。默认拒绝策略是hander,抛出了一个RejectExecutionException。

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_学习_04

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_阻塞队列_05

其实还有其它的策略方式,我直接复制粘贴了。

AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
CallerRunsPolicy 让调用者运行任务
DiscardPolicy 放弃本次任务
DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方
便定位问题
Netty 的实现,是创建一个新线程来执行任务
ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略

4.当高峰使用期过去后,会将创建的救急线程经过keepaliveTime以及TimeUnit的结果给销毁了。

根据构造方法,说一下各类线程池。

newFixedThreadPool

newFixedThreadPool,代表了定长线程池。看一下构造函数的结构。

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_阻塞队列_06

这四类线程池,都是引用的ThreadPoolExecutor的构造函数,其中,corePoolSize,以及maximumPoolSize都是设计的入参的线程数,过期时间为0,代表了当前线程池之中没有救急线程,阻塞队列采用的是LinkedBlockedQueue队列,这个队列是无界队列,如果创建的线程大于corePoolSize,就将线程放入至阻塞队列中,然后等待核心线程处理完成后进行消费。

这种适合处理任务量已知,相对比较耗时的任务。

newCatchedThreadPool

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_线程池_07

 缓冲类线程池,核心线程为0,默认创建的线程都是救急线程,没有核心线程。在业务逻辑处理完成后,60s后没有操作会被销毁。阻塞队列采用的是SynchronousQueue来进行阻塞。

这种线程池会根据任务数来进行不断的增长,没有上限,当任务执行完毕后,60s后线程被释放。适合任务比较密集,但执行时间较短的现象。

队列采用了SynchronousQueue来进行存放,这种队列的特点是,没有容量,当没有线程试图取的时候,任务是存放不进队列的。

newSingleThreadExecutor

 

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_java_08

默认情况下,线程池中的corePoolSize与maximumPoolSize长度都是1,只会创建一个线程,剩下进入的任务都会存放到LinkedBlockingQueue中。来对任务进行串行执行。

该线程池相比于当独创建一个线程的好处是,如果线程因为异常情况消亡后,会自动新创建一个线程来继续执行任务。保证线程池的工作。

newFixedThreadPool,与newSingleThreadExecutor有点类似,但是newFixedThreadPool对外暴露的是ThreadPoolExecutor,这种方式可以强转更改线程数的,而newSingleThreadExecutor则是被装饰了一层,从而不会被强转。

常用的方法

execute,submit,invokeAll(在这个里面我看到了future的get方法),invokeAny,shutdown,shutdownNow,isTerminated,isShutDown。

package com.bo.threadstudy.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ThreadPoolTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        fixedThreadPoolExecute();
//        newCatchedThreadPoolTest();
        //其实我上方的使用都是阻塞式方式,对性能的提升并不友好,真正的操作是多个操作并行进行,我这种样例还是按串行走了
//        newSingleThreadExecutorTest();

        shutdownNowTest();

    }

    //TODO 这个shutdownNow我没有测试好,后续再说
    private static void shutdownNowTest() throws InterruptedException {
        //执行方法,submit以及execute已经演示过了,试一下invokeAny以及invokeAll
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        List<Callable<Integer>> runnables = new ArrayList<>();
        AtomicInteger atomicInteger = new AtomicInteger(10);
//        invokeAllTest(executorService, runnables, atomicInteger);
//        invokeAnyTest(executorService, runnables, atomicInteger);

        //测试一下线程停止的方法
        for (int i = 0; i < 20; i++) {
            runnables.add(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    Thread.sleep(1000);
                    log.debug(atomicInteger.getAndDecrement()+"");
                    return 1;
                }
            });
        }

        //执行所有任务,我看了下,之所以shutDown的原因是源码离调用了future的get方法,卡住了,自然就出问题了
        executorService.invokeAll(runnables);
        //立刻停止线程池
        executorService.shutdownNow();
        log.debug(executorService.isShutdown()+"");
        log.debug(executorService.isTerminated()+"");
//        executorService.shutdown();
    }

    private static void invokeAnyTest(ExecutorService executorService, List<Callable<Integer>> runnables, AtomicInteger atomicInteger) throws InterruptedException, ExecutionException {
        //invokeAny相当于将线程中的所有任务都执行,取结果,取最快执行完成的那一个
        for (int i = 0; i < 20; i++) {
            runnables.add(() -> {
                //我这里配合了CAS使用,结果是没有问题的,但是在主线程中,输出的结果顺序是不一样的
                return atomicInteger.decrementAndGet();
            });
        }
        Integer integer = executorService.invokeAny(runnables);
        log.debug(integer+"");
    }

    private static void invokeAllTest(ExecutorService executorService, List<Callable<Integer>> runnables, AtomicInteger atomicInteger) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            runnables.add(() -> {
                //我这里配合了CAS使用,结果是没有问题的,但是在主线程中,输出的结果顺序是不一样的
                return atomicInteger.decrementAndGet();
            });
        }

        List<Future<Integer>> futures = executorService.invokeAll(runnables);
        futures.forEach(integerFuture -> {
            try {
                log.debug(integerFuture.get()+"");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }

    private static void newSingleThreadExecutorTest() {
        ExecutorService executorService2 = Executors.newSingleThreadExecutor();
        final int[] i = {10};
        for (int i1 = 0; i1 < 10; i1++) {
            //这不用Atomic问题也没事,因为只是一个线程在执行,不过只写成i--还是会报错的,这可能就是规范方面的事
            executorService2.execute(() -> {
                i[0] = i[0] -1;
                log.debug("{}", i[0]);
            });
        }
        //线程池停止,原先的服务没有停的,现在的任务停了
        executorService2.shutdown();
    }

    private static void newCatchedThreadPoolTest() throws InterruptedException, ExecutionException {
        AtomicInteger atomicInteger = new AtomicInteger(10);
        ExecutorService executorService1 = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            //这儿的话肯定是线性执行了,因为有get阻塞,atomicInteger有点多余
            Future<Integer> submit = executorService1.submit(() -> {
                return atomicInteger.decrementAndGet();
            });
            log.debug(submit.get()+"");
        }
    }

    private static void fixedThreadPoolExecute() {
        //newFixedThreadPool,定长线程池的execute执行
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        AtomicInteger atomicInteger = new AtomicInteger(10);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                log.debug(atomicInteger.decrementAndGet()+"");
            });
        }
    }

}

newSchudledThreadPool

shutdownNow 后线程池还存在嘛 线程池stop和shutdown_开发语言_09

 

任务调度线程池,这个线程池采用的也是ThreadPoolExecutor的实现,可以看到corePoolSize是传入的参数,救急线程数无限制。生存时间是0?只是队列采用了delayedWorkQueue。

这个线程池可以设置定时任务来执行,即可以在规定的时间来对任务进行执行。

内部的话,主要方法就三个,

schedule()方法,多久之后执行当前任务,只执行一次。

scheduleAtFixedRate,设计一个初始化时间,然后在间隔多久时间后开始执行,如果执行的时间超过间隔时间,会按执行时间先走。执行多次的任务。

scheduledWithFixedDelay,即执行完当前任务后,间隔多久来执行这个任务,执行多次的任务。

package com.bo.threadstudy.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
public class NewSchudledThreadPoolTest {
    public static void main(String[] args) {
        //设置长度为10吧
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//        extracted(scheduledExecutorService);
        extracted1(scheduledExecutorService);
        //这个任务的第三个参数,是指的当前时间执行完成之后的间隔,即两秒执行完任务,等待1秒后继续执行任务
//        scheduledExecutorService.scheduleWithFixedDelay(() -> {
//            try {
//                Thread.sleep(2000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            log.debug("我在测试");
//        },0,1,TimeUnit.SECONDS);
//        //执行多次,就是从线程池中直接再取一个线程出来进行操作,明白了
//        scheduledExecutorService.scheduleWithFixedDelay(() -> {
//            try {
//                Thread.sleep(2000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            log.debug("我在测试");
//        },0,2,TimeUnit.SECONDS);


    }

    private static void extracted1(ScheduledExecutorService scheduledExecutorService) {
        //定时任务
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            //看看任务执行完之前是否会走任务,该方法必须得执行完成毕后,才可以走下一趟
            //这个其实是我执行完成后,然后超过了当前规定的时间,那么下一个方法立刻执行,不考虑别的
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("我在测试");
        },0,1500,TimeUnit.MILLISECONDS);
    }

    private static void extracted(ScheduledExecutorService scheduledExecutorService) {
        scheduledExecutorService.schedule(() -> {
            //1秒后执行
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("我在测试");
            scheduledExecutorService.shutdown();
        },1, TimeUnit.SECONDS);
    }

}

线程池异常抓取

在线程池执行任务出现异常时,execute会把异常给爆出来,而submit是不会爆异常的,所以针对这种现象,有两种解决方案:

try-catch抓取异常

利用future来返回结果,在返回结果后这个异常就可以爆出来了。

package com.bo.threadstudy.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 关于线程处理异常
 */
@Slf4j
public class ThreadPoolException {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future<Integer> submit = executorService.submit(() -> {
            //试了一下,executor方法是可以抛出异常的,而submit是没有办法抛出的,两种解决方法
            //第一种,trycatch抛出异常,第二种通过返回结果抛出异常
            int i = 1 / 0;
            return i;
        });
        log.debug(submit.get()+"");
    }
}

ForkJoinPool

forkJoinPool是1.7引入的线程池,该线程池的目的,是采用了分治思想来,对CPU密集型运算来进行拆分。

所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计
算,如归并排序、斐波那契数列、都可以用分治思想进行求解
Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率
Fork/Join 默认会创建与 cpu 核心数大小相同的线程池

其实总结就是一句话,涉及到动态规划,递归类型的算法,可以采用ForkJoinPool线程池来进行操作。

假设我内部存在多个递归调用,如果是单线程的话,一个递归在执行,另一个递归得等待这个递归执行完成后,才能继续执行,就像快速排序一般,存在两个递归的这种。但如果使用forkJoinPool的方式,则开两个线程处理任务,相互之间并不影响。性能提升。

package com.bo.threadstudy.eight;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

@Slf4j
public class ForkJoinPoolTest {

    public static void main(String[] args) {
//        ForkJoinPool forkJoinPool = new ForkJoinPool(10);
//        log.debug(forkJoinPool.invoke(new ForkTask(6))+"");

        ForkJoinPool forkJoinPool = new ForkJoinPool(10);
        log.debug(forkJoinPool.invoke(new ForkTaskNew(1,7))+"");
    }

}

@Slf4j
class ForkTaskNew extends RecursiveTask<Integer>{
    private Integer begin;

    private Integer end;

    public ForkTaskNew(Integer begin, Integer end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        //起始值初始值比较,作为终止条件
        if(begin == end){
            return begin;
        }
        if(begin == end - 1){
            return begin+end;
        }
        //我这块写的性能不够快,二分法相比递归其实更快,因为多用了线程
//        ForkTaskNew forkTaskNew = new ForkTaskNew(begin + 1, end - 1);
//        forkTaskNew.fork();
//        Integer i = 0;
//        try {
//            i = forkTaskNew.get();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }
//
//        return begin+end+i;
        Integer mid = (begin+end)/2;
        ForkTaskNew forkTaskNew1 = new ForkTaskNew(begin, mid);
        forkTaskNew1.fork();
        ForkTaskNew forkTaskNew2 = new ForkTaskNew(mid+1, end);
        forkTaskNew2.fork();
        Integer i1 = null;
        Integer i2 = null;
        try {
             i1 = forkTaskNew1.get();
             i2 = forkTaskNew2.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        return i1+i2;
    }
}


/**
 * 提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下
 * 面定义了一个对 1~n 之间的整数求和的任务
 */
@Slf4j
class ForkTask extends RecursiveTask<Integer>{

    private Integer cur;

    public ForkTask(Integer cur) {
        this.cur = cur;
    }

    //在这里实现一个累加的方法,利用递归的方式来添加
    //后期我可以试一下快速排序使用forkJoinPool来优化,面试官虽然想要的肯定不是这个答案

    //这个有点类似Runable中的run方法,代表了执行任务
    @Override
    protected Integer compute() {
        //此次先以线性的方式来表达一下,
//        return addTest01();
        //累加的方式,就像原先的方式,实际运行的次数很明显是cur,而如果从中间取值,那么创建的线程就是n/2


        return null;
    }

    private Integer addTest01() {
        if(cur == 1){
            log.debug("递归运行到最下层");
            return 1;
        }
        ForkTask forkTask = new ForkTask(cur - 1);
        //异步执行当前任务
        forkTask.fork();
        Integer value = null;
        try {
            value = cur+forkTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return value;
    }


}