深入理解Java线程池及源码解析

为什么使用线程池
  在Java虚拟机的线程模型中,Java线程(Thread)被一对一映射为操作系统内核线程。Java线程启动时会创建一个操作系统内核线程;当Java线程终止时,操作系统内核线程也会被回收,这些操作会耗费资源。所以频繁的创建启动、销毁线程,会耗费操作系统较多资源。开发过程中,合理地使用线程池有2个好处:
  1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2.提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源, 还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

线程池实现原理
当向ThreadPoolExecutor执行execute()方法提交任务时,线程池处理流程如下:
在这里插入图片描述
图中线程池执行流程如下:
  1)判断线程池线程数量是否已经达到核心线程数。如果否,则创建一个新的线程 来执行任务;如果是,则进入下个流程。
  2)判断阻塞队列是否已满。如果否,则添加任务到阻塞队列,等待执行;如果是,则进入下个流程。
  3)判断线程池线程数是否达到最大线程。如果否,创建一个线程来执行任务;如果是,则执行对应的拒绝策略。

ThreadPoolExecutor中线程执行任务流程如下:
在这里插入图片描述
图中线程池线程执行流程如下:
  1)新创建一个线程时,会让这个线程执行当前任务。
  2)这个线程执行完图中1任务后,会一直循环从阻塞队列BlockingQueue中获取任务来执行。当队列为空时,线程会被阻塞;当有新任务添加到阻塞队列时,阻塞的线程会被唤醒继续执行任务。

怎么使用线程池
线程池选择
在concurrent包下,Executors类中可以创建如下常见的线程池:
  1.newCachedThreadPool:队列不存放任务,每次提交任务时,空闲线程不足都将创建新线程,线程空闲时间超过60秒将被回收。
  2.newFixedThreadPool:创建固定线程数的线程池,使用的无界阻塞队列,可能存在OOM风险。
  3.newSingleThreadExecutor:单线程线程池,可用于按顺序执行任务。
  4.newScheduleThreadPool:可用于定时执行任务。

线程池创建
  通常我们可以选择ThreadPoolExecutor手动指定参数来创建一个线程池:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

  1)corePoolSize:线程池核心线程数。当提交一个任务时,如果线程池线程数小于corePoolSize,则会新创建一个线程来执行任务。
  2)maximumPoolSize:线程池最大线程数。
  3)keepAliveTime:超过核心线程数小于最大线程数的线程,如果空闲时间超过这个值,将会被回收掉。
  4)TimeUnit:keepAliveTime时间单位。
  5)BlockingQueue:阻塞任务队列。
  常用的几种阻塞队列: ArrayBlockingQueue(数组结构)、LinkedBlockingQueue(链表结构)、SynchronousQueue(不存储元素)、PriorityBlockingQueue(带优先级)。
  6)ThreadFactory:创建线程的工厂。可以通过线程工厂给线程设置名称。
new ThreadFactoryBuilder().setNameFormat(“测试线程池-task-%d”).build()
  7)RejectedExecutionHandler:当队列和线程池线程数都满了,执行拒绝策略。
常用线程池拒绝策略:
  AbortPolicy:任务满时,直接抛出异常。
  DiscardPolicy:任务满时,直接将任务丢弃,会造成数据丢失风险。
  DiscardOldestPolicy:任务满时,将队列头任务丢弃,也会造成数据丢失风险。
  CallerRunsPolicy:任务满时,由提交任务线程自己执行。
建议将线程池对象设置为静态变量或单例。以前在公司代码中有发现,系统每接收到一个请求,业务代码就创建一个线程池,且使用完后未shutdown(),线程会一直存活,这些线程对象和线程池对象不能被GC掉,时间一久系统很大可能出现OOM。

向线程池提交任务
  execute()方法用于提交不需要返回值的任务,无法判断任务是否被线程池执行成功。submit()方法会返回一个future类型结果,可以通过future的get()方法获取返回值,get()方法会阻塞当前线程直到任务完成。
线程池参数设置
  任务类型分为:CPU密集型和IO密集型。CPU密集型任务压力主要在CPU上,所以配置的线程数不用太多,例如配置Ncpu + 1个线程;IO密集型任务压力主要在IO上,CPU比较空闲,所以配置的线程数需要多一些,例如2 * Ncpu。
  任务队列尽量选择有界队列,有界队列能增加系统的稳定性和预警能力,无界队列系统可能会有OOM风险。

ThreadPoolExecutor线程池源码分析
execute()方法逻辑:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // ctl是一个32位原子变量,高3位表示线程池状态,低29位表示线程数量。
    int c = ctl.get();
    // 1.如果线程数量小于核心线程数,新创建线程执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2.如果线程池状态为RUNNING,尝试将任务添加到阻塞队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
      addWorker(null, false);
    }
    // 3.如果2中添加任务到阻塞队列失败,尝试创建非核心线程执行任务
    else if (!addWorker(command, false))
    // 如果创建线程失败,执行拒绝策略
    reject(command);
}

逻辑梳理:
  1.thread会包装成Worker对象进行任务执行。
  2.需要注意的是,ctl字段使用的是32位原子int字段,前3位表示线程池状态,后29位表示线程数量。通过对应位运算操作,可以获取到线程池的状态和线程数量,且每次线程数量和状态修改,都是对ctl的CAS操作,这样就保证了两个值始终是一致的。
addWorker(command, true)中代码太多,注意下面这两部分就行了:

compareAndIncrementWorkerCount(c)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}

创建线程前,会通过CAS作将ctl加1。
下面这部分代码是将thread包装成Worker对象:

w = new Worker(firstTask);
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Worker(Runnable firstTask) {
    setState(-1);
    // 将runnable对象设置为first任务。    
    this.firstTask = firstTask;
    // 获取线程池的线程工厂,传入当前worker对象创建线程
    this.thread = getThreadFactory().newThread(this);
}

逻辑梳理:
  1.将提交任务的runnable对象设置为first任务。
  2.获取线程池线程工厂,创建线程。Worker实现了Runnable接口,这里创建线程时传入的是worker对象,不是提交任务时的runnable对象,所以,调用thread.strat()时,是执行worker对象的run()方法。
执行thread.strat()方法,会调用worker对象run()方法,run()方法会调用runWorker(Worker w)方法,源码逻辑:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 注意这行代码,第一个任务执行完后,会循环从阻塞队列中获取任务执行
        // 当从队列中获取任务为空时,线程会被阻塞;当有新任务添加到阻塞队列
        // 后,阻塞的线程会依次被唤醒继续执行任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
          }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

关闭线程池,shutdown()方法逻辑:

public void shutdown() {

    // 1.获取到线程池全局锁,保证只能同时有一处关闭操作

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 2.权限校验
        checkShutdownAccess();
        // 3.CAS操作将ctl高3位设置为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 4.中断线程池线程
        interruptIdleWorkers();
        onShutdown();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值