线程池从入门到精通

了解线程池

为什么使用线程池?

  • 减少线程创建销毁的次数;
  • 统一管理线程;
  • 调整参数合理利用CPU和内存;

1 线程池基本使用

1.1 线程池参数

线程池参数包含:核心线程数最大线程数存活时间存活时间单位任务的存储队列线程工厂拒绝策略

在这里插入图片描述

1.2 工作队列和自定义线程工厂、拒绝策略

多线程中常用的队列是线程安全的BlockingQueue,它的子类有ArrayBlockingQueueLinkedBlockingQueueSynchronousQueuePriorityBlockingQueue
上面的队列操作函数:有对于不能立即满足但可能在将来某一时刻可以满足的操作,有以下四种不同形式的处理方式:

  • 抛出一个异常
  • 返回一个特殊值(null 或 false,具体取决于操作)
  • 在操作可以成功前,无限期地阻塞当前线程
  • 在放弃前只在给定的最大时间限制内阻塞
    在这里插入图片描述
    自定义线程工厂:
package com.maokeyang.transactional.thread;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

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;

    public MyThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                Thread.currentThread().getThreadGroup();
        //此处只修改了一下名字前缀
        namePrefix = "自定义-" +
                poolNumber.getAndIncrement() +
                "-thread-";
    }

    @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);
        }
        // 异常处理的一种方式
		t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println("线程:" + t + ", 异常:" + e);
            }
        });
        return t;
    }
}

自定义拒绝策略:

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

使用测试类:

public class ThreadPoolExecutorTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = new ThreadPoolExecutor(
                1,
                1,
                0,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                new MyThreadFactory(), new MyRejectHandler());
        executorService.execute(new Task());
        executorService.execute(new Task());
        executorService.execute(new Task());

    }
}

public class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("进入task方法...");
        int i = 1/0;
    }
}

运行结果:
在这里插入图片描述

2 异常如何处理?

线程池的提交方式一共两种 submitexecute

public class ThreadPoolException {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService executorService= Executors.newFixedThreadPool(1);
        //当线程池抛出异常后 submit无提示,其他线程继续执行
        executorService.submit(new Task());
        //当线程池抛出异常后 execute抛出异常,其他线程继续执行新任务
        executorService.execute(new Task());
    }
}

//任务类
class Task implements  Runnable{
    @Override
    public void run() {
        System.out.println("进入了task方法!!!");
        int i=1/0;
    }
}

运行结果:
在这里插入图片描述
可以看到: submit 不打印异常信息,而execute则会打印异常信息!submit的方式不打印异常信息,显然在生产中,是不可行的,因为我们无法保证线程中的任务永不异常,而如果使用submit的方式出现了异常,直接如上写法,我们将无法获取到异常信息,做出对应的判断和处理,所以下一步需要知道如何获取线程池抛出的异常!
submit()想要获取异常信息就必须使用get()方法!!

        //当线程池抛出异常后 submit无提示,其他线程继续执行
        Future<?> submit = executorService.submit(new task());
        submit.get();

如何处理异常呢?

  • run方法里使用try - catch
  • 使用Thread.setDefaultUncaughtExceptionHandler方法捕获异常
    方案一中,每一个任务都要加一个try-catch 实在是太麻烦了,而且代码也不好看,那么这样想的话,可以用Thread.setDefaultUncaughtExceptionHandler方法捕获异常。
    应用在线程池中如下所示:重写它的线程工厂方法,在线程工厂创建线程的时候,针对每一个新建的Thread对象都赋予UncaughtExceptionHandler处理器对象。处理方式:自己顶一个实现UncaughtExceptionHandler的类,创建此类对象,对Thread对象进行设置。
  • 重写ThreadPoolExecutor类的afterExecute(Runnable r, Throwable t)方法进行异常处理

3 线程池钩子(beforeExecute && afterExecute)

通过这两钩子可以实现线程执行前逻辑、执行中异常打印

public class ThreadPoolExecutorTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService executorService = new ThreadPoolExecutor(
                1,1,0,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<>(1),
                new MyThreadFactory(), new MyRejectHandler()) {
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                super.beforeExecute(t, r);
                System.out.println("Thread beforeExecute: " + t);
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                System.out.println("afterExecute里面获取到异常信息,处理异常" + t.getMessage());

            }
        };
        executorService.execute(new Task());
    }
}

4 线程池停止

  • shutdown() 等待正在执行和队列中的任务执行完毕,才关闭。 但是添加新任务会直接拒绝
    可以根据isShutDown()判断TRUE 或者FALSE ; 另外 isTerminated() 判断所有任务是否执行完毕
  • shutdownNow() : interrupt 当前执行的任务,不再添加新任务,返回等待的任务

5 线程池实现原理、源码分析

线程池运行状态包含:

  • RUNNING:接收新的任务并对任务队列里的任务进行处理;
  • SHUTDOWN:不再接收新的任务,但是会对任务队列中的任务进行处理;
  • STOP:不接收新任务,也不再对任务队列中的任务进行处理,并中断正在处理的任务;
  • TIDYING:所有任务都已终止,线程数为0,在转向TIDYING状态的过程中,线程会执行terminated()钩子方法,钩子方法是指在本类中是空方法,而在子类中进行具体实现的方法;
  • TERMINATED:terminated()方法执行结束后会进入这一状态,表示线程池已关闭。

线程池运行状态存储在AtomicInteger类型的变量ctl的最高三位中,因此各种状态所对应的整型变量的二进制格式除了最高三位,其余都是0 (如代码注释所示)。这些变量的操作会涉及到一些位运算和原子操作,现在只需要了解这些状态变量从小到大的顺序是RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED,另外很重要的一点就是这些状态的转化只能从小到大,不能从大到小。它们在源码中对应的变量如下:

// RUNNING: 十进制:-536870912  二进制:11100000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS; 
// SHUTDOWN: 十进制:0  二进制:0
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// STOP: 十进制:536870912  二进制:00100000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS;
// TIDYING: 十进制:1073741824  二进制:01000000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;
// TERMINATED: 十进制:1610612736  二进制:01100000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;

// COUNT_BITS: 29
private static final int COUNT_BITS = Integer.SIZE - 3;

线程池运行状态关系图

在这里插入图片描述
线程池的状态存储在一个 AtomicInteger 类型的变量 ctl 中,其实这个变量还存储了另一个线程池的重要信息,那就是线程数量 (workerCount)。从名字就可以看出,AtomicInteger 类对 int 类型进行了封装,可以对整数进行各种原子操作,适合在多线程环境中使用。大家都知道 int 类型有32位,而线程池的运行状态一共有5种,所以 ctl 的高三位足以表示所有的线程池运行状态 (23 = 8 > 5)。而另一个变量 workerCount 正好存储在其低29位中 (取值范围是0到229-1)。ThreadPoolExecutor 的源码中有很多对 ctl 变量的操作,在这里先把他们搞懂,后面就不会懵逼了。

// 使用一个AtomicInteger类型的变量ctl同时控制线程池运行状态和线程数量,初始运行状态
// 为RUNNING,线程数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// CAPACITY: 十进制: 536870911 二进制: 00011111111111111111111111111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;


// 获取线程池当前状态,CAPACITY取反后高三位都是1,低29位都是0,再和c进行按位与运算,即可获得runState变量
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// CAPACITY高三位都是0,低29位都是0,和c进行按位与运算即可获得workerCount变量
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 初始化ctl变量,runState和workerCount进行按位或运算即可将这两个变量存储在一个变量中
private static int ctlOf(int rs, int wc) { return rs | wc; }

// 部分方法
// 判断线程池当前运行状态是否小于给定值
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}
// 判断线程池当前运行状态是否大于等于给定值
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}
// 判断线程池是否处于RUNNING状态
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}
5.1 线程池实现任务复用的原理

相同线程执行不同的任务,对应源码:

6 线程池使用注意点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值