白话讲懂java线程池的使用和源码

线程池是一个稀缺资源,如果被无限的创建,会消耗系统资源,产生竞争死锁,降低系统效率。对于每个线程执行时间很少的这种场景就很适合让线程重用。否则可能出现花在系统创建销毁线程上的时间比线程真正执行的事件还长,消耗的资源还多的情况。

应用场景:

  • 单个任务处理时间比较短
  • 需要处理的任务数量比较大

线程池的优势:

  1. 重用线程,减少线程创建销毁的系统消耗,提高性能。
  2. 提高响应速度(不用创建销毁)
  3. 提高线程的管理性,使线程可以统一的分配调休监控

线程池的创建

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

任务提交

1、public void execute()  //提交任务无返回值
2、public Future<?> submit()  //任务执行完成后有返回值


参数解释

corePoolSize
线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
keepAliveTime
线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
unit
keepAliveTime的单位;
workQueue
用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
4、priorityBlockingQuene:具有优先级的无界阻塞队列; 

线程池结构:

例如我们定义了一个, 这段代码会创建一个核心5个核心线程,最多10个线程,blocking queue最多放5个。

1. 刚开始的五个线程每次提交会将创建核心线程然后用核心线程来执行他们。

2.第6-10个线程在被提交进来会将他们放到blocking queue里面去。

3.当blockingqueue也满了之后会在继续创建非核心线程来执行。

4.当非核心线程也满了这时候回开始执行拒绝策略maximumPoolSize - corePoolSize = 非核心线程最大数

 

三种阻塞队列:
    BlockingQueue<Runnable> workQueue = null;
    workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
    workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
    workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界

四种拒绝策略:
    RejectedExecutionHandler rejected = null;
    rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
    rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
    rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
    rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
    自定义拒绝策略,如果以上的几种拒绝策略都不满足条件,则可以实现RejectedExceptionHandler接口,并将自己的逻辑写在rejectedExcepiton方法内。


五种线程池:
    ExecutorService threadPool = null;
    threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
    threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
    threadPool = Executors.newScheduledThreadPool(2);
    threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
    threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多   

源代碼分析:

1. 首先理解一下,解源码中的方法,

workerCountOf(c)
runStateOf(c)

如下,代码在源码中通过一个int型4字节一共32位,来存储线程数量,和线程池状态两个信息。32位的左边3位来存储状态,右边29位来存储线程数量。通过CAPACITY的与或运算来快速获取线程数量和线程状态。注释中有例子,

public class Test {
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    //c  ======>  01000000000000000011111000000000
    //          & 11100000000000000000000000000000
    //          = 01000000000000000000000000000000 
    //     equals 01000000000000000000000000000000 -> TIDYING
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    public static void main(String[] args) {
        System.out.println(COUNT_BITS);
        System.out.println(Integer.toBinaryString(CAPACITY));   //00011111111111111111111111111111
        System.out.println(Integer.toBinaryString(~CAPACITY));  //11100000000000000000000000000000
        System.out.println(Integer.toBinaryString(RUNNING));    //11100000000000000000000000000000
        System.out.println(Integer.toBinaryString(SHUTDOWN));   //00000000000000000000000000000000
        System.out.println(Integer.toBinaryString(STOP));       //00100000000000000000000000000000
        System.out.println(Integer.toBinaryString(TIDYING));    //01000000000000000000000000000000
        System.out.println(Integer.toBinaryString(TERMINATED)); //01100000000000000000000000000000
    }

2. 源码分析

第一步:源码入口: ThreadPoolExecutor.execute(Runnable command)

1. 如果当前线程数小于核心线程池数,直接创建核心线程。

2. 如果当前线程数大于等于核心线程池数,但是Queue里可以加入线程。

3.直接启动非核心线程执行。

4.启动非核心线程失败,执行拒绝策略。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        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);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

第二步:进入第一步分支1. -- addWorker(command, true)

注意,该方法中用到大量的自旋,如下,其原因是该方法中有用到CAS操作。compareAndIncrementWorkerCount(c),因为cas操作时操作完的结果值与预期结果不一致时需要重新操作。就是在缓存一致性协议中的I(Invalid)值,因为在大量并发的操作中不能保证自己的操作一定能成功,所以需要重试。

for (;;) {
...
}

1. 当前线程状态必须是running。

2.重新检查当前线程池中线程数。如果大于coresize或maxcoresize退出。

3.当前线程池的线程计数器+1.

4.new 一个 new Worker(firstTask)。

5.加锁,添加启动Worker过程中不能多个线程同时执行。

6. workers.add(w)。

7.启动worker的run方法。

...
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
   firstTask == null &&
   ! workQueue.isEmpty())) //1
return false;
...
if (wc >= CAPACITY ||
	wc >= (core ? corePoolSize : maximumPoolSize))//2
	return false;
if (compareAndIncrementWorkerCount(c))//3
...
w = new Worker(firstTask);//4
final Thread t = w.thread;
...
final ReentrantLock mainLock = this.mainLock;//5
mainLock.lock();
...
	workers.add(w);//6
...
if (workerAdded) {
	t.start();//7
...
 private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

第三步,进入第二步方法7 -- t.start().

在第二步的代码中,对t进行了如下赋值:

w = new Worker(firstTask);
final Thread t = w.thread;

在Worker的构造方法中又有如下thread赋值,this.firstTask = firstTask; 这里的firstTask就是第一步中的command,我们自己定义的线程。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
Worker(Runnable firstTask) {
    setState(-1);
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this); 
    //w.thread的赋值是new Thread(this)其中this就是Worker本身.
}

所以t.start(),调用的就是Worker的run方法:

public void run() {
    runWorker(this);
}

进入runWorker(this)方法,该方法有以下步骤:

1.  取出用户自己定义的任务 Runnable task = w.firstTask;

2. 判断该任务是否为空。如果不为空则取出该任务。

3..如果任务为空,则调用getTasks取出一个任务。

4. 调用该任务的run方法,及开始执行用户自己用的线程。

Thread wt = Thread.currentThread();
Runnable task = w.firstTask;//1
...
try {
while (task != null || (task = getTask()) != null) {//2,3
...
task.run();//4
...

第四步,getTask().

1. check queue 是否为 空。如果为空返回null。

2.从queue中取出一个元素,如果在keepAliveTime事件内能够取出则用poll如果不能就用take一直阻塞等着取。

workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();
...
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { //1
	decrementWorkerCount();
	return null;
}
...
try {
Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS):workQueue.take(); //2
...

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值