(ROOT)并发编程5-线程池

用到的锁

ReentrantLock , CAS

实现关系

Excutor接口 ,只声明了一个方法execute(Runnable)

->继承

ExcutorService接口,加入了shutdown,submit,invokeAll(),invokeAny等方法

->实现

AbstractExcutorService抽象类,基本实现了ExecutorService中声明的所有方法;

->继承

ThreadPoolExcutor,ForkJoinPool

ForkJoinPoll:先将任务分解,最后再汇总
在这里插入图片描述

线程池构造方法

  java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

  在ThreadPoolExecutor类中提供了四个构造方法:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

构造器中各个参数的含义:

从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

  • corePoolSize:

  • 核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:

  • 线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:

  • 表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:

  • 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

  • workQueue:

  • 一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

ArrayBlockingQueue;

LinkedBlockingQueue;

SynchronousQueue;(固定为1)

TransferQueue(固定长度)

  ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

  • threadFactory:

  • 线程工厂,实现newThread方法,主要用来创建worker所属的线程;
  • handler:

  • 表示当拒绝处理任务时的策略,有以下四种取值:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

一般会自定义策略比如把消息保存下来(尤其是订单之类的)保存到kafka,redis或数据库,日志等

自定义策略
import java.util.concurrent.*;

public class T14_MyRejectedHandler {
    public static void main(String[] args) {
        ExecutorService service = new ThreadPoolExecutor(4, 4,
                0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),
                Executors.defaultThreadFactory(),
                new MyHandler());
    }

    static class MyHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            //log("r rejected")
            //save r kafka mysql redis
            //try 3 times
            if (executor.getQueue().size() < 10000) {
                //try put again();
            }
        }
    }
}

核心方法

execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。

  submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。

  shutdown()和shutdownNow()是用来关闭线程池的。

  还有很多其他的方法:

  比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法,有兴趣的朋友可以自行查阅API。

submit方法调用

submit是异步的,交给线程池后主线程该干嘛干嘛返回值可由future的get方法得到,该方法是阻塞的

Callable -> Runnable + ret

Future -> 存储执行的将来才会发生的结果

import java.util.concurrent.*;

public class T03_Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> c = new Callable() {
            @Override
            public String call() throws Exception {
                return "Hello Callable";
            }
        };

        ExecutorService service = Executors.newCachedThreadPool();
        Future<String> future = service.submit(c); //异步

        System.out.println(future.get());//阻塞
        service.shutdown();
    }
}

扩展

Futuretask

FutureTask -> Future + Callable

/**
 * 认识future
 * 异步
 */
package com.mashibing.juc.c_026_01_ThreadPool;

import java.util.concurrent.*;

public class T06_00_Future {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		FutureTask<Integer> task = new FutureTask<>(()->{
			TimeUnit.MILLISECONDS.sleep(500);
			return 1000;
		}); //new Callable () { Integer call();}
		
		new Thread(task).start();
		
		System.out.println(task.get()); //阻塞


	}
}

CompletableFuture

可用来管理多个Future的结果,对各种各样的结果进行组合处理。你可以去查查它的API~
提供了很多非常好用的接口,十分友好!

示例:假设你能够提供一个服务,这个服务查询各大电商网站同一类产品的价格并汇总展示,你用CompletableFuture开启三个线程来完成这个任务

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class T06_01_CompletableFuture {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start, end;

        /*start = System.currentTimeMillis();

        priceOfTM();
        priceOfTB();
        priceOfJD();

        end = System.currentTimeMillis();
        System.out.println("use serial method call! " + (end - start));*/

        start = System.currentTimeMillis();

        CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(() -> priceOfTM());
        CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(() -> priceOfTB());
        CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(() -> priceOfJD());

        CompletableFuture.allOf(futureTM, futureTB, futureJD).join(); // 提供对于一堆任务的管理:这三个任务全部完成之后,才能继续向下运行
//        CompletableFuture.anyOf(futureTM, futureTB, futureJD).join();  // 任意一个任务完成,就能继续向下运行

        CompletableFuture.supplyAsync(() -> priceOfTM())
                .thenApply(String::valueOf)
                .thenApply(str -> "price " + str)
                .thenAccept(System.out::println);


        end = System.currentTimeMillis();
        System.out.println("use completable future! " + (end - start));

        try {
            System.in.read();//因为里面全是异步,所以需要阻塞一下,才能正常的等待它们输出,不然主线程先结束了。。
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static double priceOfTM() {
        delay();
        return 1.00;
    }

    private static double priceOfTB() {
        delay();
        return 2.00;
    }

    private static double priceOfJD() {
        delay();
        return 3.00;
    }

    /*private static double priceOfAmazon() {
        delay();
        throw new RuntimeException("product not exist!");
    }*/

    private static void delay() {
        int time = new Random().nextInt(500);
        try {
            TimeUnit.MILLISECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("After random %s sleep!\n", time);
    }
}

jdk中的工具类

对数组操作的工具类:Arrays

对容器操作的工具类:Collections

对线程操作的工具类:Executors

Executors中方法

1 Executors.newSingleThreadExecutor()

特点:LinkedBlockingQueue队列长integer.max,不超时只有一个线程

为什么要有单线程线程池:

1.线程池有任务队列

2.线程池有完整的生命周期管理

2 Executors.newCachedThreadPool()

特点:没有核心线程,60s回收,来一个起一个线程,用完就删除

syncqueue:必须同时进一个出一个,本身不存储元素cap==0,

可能起的线程数会非常多。

3 Executors.newFixedThreadPool(cpuCoreNum);

固定线程数的好处是:

适合做一些并行的计算,比如你要找1-200000之内所有的质数,你将这个大任务拆成4个小线程,共同去运行。
利用线程池进行并行的计算,肯定比串行计算要更快。

并发和并行的区别:

并发指任务提交,并行指任务执行;
并行是多个CPU同时处理,并发是多个任务同时过来;
并行是并发的子集

Catched vs Fixed引出如何调整线程池大小

如何选择(建议都不选,自定义)

加入任务来的时候忽高忽低用catched

如果任务来的平稳就用fixed

按阿里的Java开发手册,这两种都不用,各有缺点,自己估算精确定义

4 Executors.newScheduledThreadPool(4);

定时任务线程池,专门用来执行定时任务,采用DelayedWorkQueue来实现


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

public class T10_ScheduledPool {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
        service.scheduleAtFixedRate(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        }, 0, 500, TimeUnit.MILLISECONDS);
    }
}

定时任务框架:quarz,cron

5. Executors.newWorkStealingPool();

WorkStealingPool 本质上还是一个 ForkJoinPool

由于一个线程有自己的队列,因此拿自己的队列任务无线程冲突不需要加锁,但stealing其他线程的队列任务需要加锁


import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class T11_WorkStealingPool {
    public static void main(String[] args) throws IOException {
        ExecutorService service = Executors.newWorkStealingPool();
        System.out.println(Runtime.getRuntime().availableProcessors());

        service.execute(new R(1000));
        service.execute(new R(2000));
        service.execute(new R(2000));
        service.execute(new R(2000)); //daemon
        service.execute(new R(2000));

        //由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出
        System.in.read();
    }

    static class R implements Runnable {
        int time;
        R(int t) {
            this.time = t;
        }

        @Override
        public void run() {
            try {
                TimeUnit.MILLISECONDS.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(time + " " + Thread.currentThread().getName());
        }
    }
}

ForkJoinPool

核心:将任务分解,执行后再将结果汇总

RecursiveAction:不带返回值,分解任务可以采用重写compute方法

RecursiveTask:重写compute()带返回值

import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;

public class T12_ForkJoinPool {
   static int[] nums = new int[1000000];
   static final int MAX_NUM = 50000;
   static Random r = new Random();
   
   static {
      for(int i=0; i<nums.length; i++) {
         nums[i] = r.nextInt(100);
      }
    }
    static class AddTaskRet extends RecursiveTask<Long> {
      
      private static final long serialVersionUID = 1L;
      int start, end;
      
      AddTaskRet(int s, int e) {
         start = s;
         end = e;
      }

      @Override
      protected Long compute() {
         
         if(end-start <= MAX_NUM) {
            long sum = 0L;
            for(int i=start; i<end; i++) sum += nums[i];
            return sum;
         } 
         
         int middle = start + (end-start)/2;
         
         AddTaskRet subTask1 = new AddTaskRet(start, middle);
         AddTaskRet subTask2 = new AddTaskRet(middle, end);
         subTask1.fork();
         subTask2.fork();
         
         return subTask1.join() + subTask2.join();
      }
      
   }
   
   public static void main(String[] args) throws IOException {
      /*ForkJoinPool fjp = new ForkJoinPool();
      AddTask task = new AddTask(0, nums.length);
      fjp.execute(task);*/

      T12_ForkJoinPool temp = new T12_ForkJoinPool();

      ForkJoinPool fjp = new ForkJoinPool();
      AddTaskRet task = new AddTaskRet(0, nums.length);
      fjp.execute(task);
      long result = task.join();
      System.out.println(result);
      
      //System.in.read();
      
   }
}

如何定义线程池

阿里《Java开发手册》建议自定义线程池,所以我们先来看看如何自定义一个线程池。

在这里插入图片描述

源码解析

维护两个集合:

  • 线程集合
  • 任务集合

用的是HashSet

常量及常量相关操作

// 1. `ctl`,可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 2. `COUNT_BITS`,`Integer.SIZE`为32,所以`COUNT_BITS`为29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 3. `CAPACITY`,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
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
// 5. `runStateOf()`,获取线程池状态,通过按位与操作,低29位将全部变成0
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 6. `workerCountOf()`,获取线程池worker数量,通过按位与操作,高3位将全部变成0
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 7. `ctlOf()`,根据线程池状态和线程池worker数量,生成ctl值
private static int ctlOf(int rs, int wc) { return rs | wc; }

/*
 * Bit field accessors that don't require unpacking ctl.
 * These depend on the bit layout and on workerCount being never negative.
 */
// 8. `runStateLessThan()`,线程池状态小于xx
private static boolean runStateLessThan(int c, int s) {
    return c < s;
}
// 9. `runStateAtLeast()`,线程池状态大于等于xx
private static boolean runStateAtLeast(int c, int s) {
    return c >= s;
}

构造方法

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    // 基本类型参数校验
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    // 空指针校验
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    // 根据传入参数`unit`和`keepAliveTime`,将存活时间转换为纳秒存到变量`keepAliveTime `中
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

execute方法

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();
    // worker数量比核心线程数小,直接创建worker执行任务
    if (workerCountOf(c) < corePoolSize) {
        // true 表示核心线程, addWorker会去判断线程池状态
        if (addWorker(command, true)) 
            return;
        c = ctl.get();
    }
    // worker数量超过核心线程数,任务直接进入队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。
        // 这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0,此时添加一个非核心线程,由于任务已到队列因此这里不需要传,等worker自己取
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。
    // 这儿有3点需要注意:
    // 1. 线程池不是运行状态时,addWorker内部会判断线程池状态
    // 2. addWorker第2个参数表示是否创建核心线程
    // 3. addWorker返回false,则说明任务执行失败,需要执行reject操作
    else if (!addWorker(command, false))
        reject(command);
}

addWorker

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    // 外层自旋
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 这个条件写得比较难懂,我对其进行了调整,和下面的条件等价
        // (rs > SHUTDOWN) || 
        // (rs == SHUTDOWN && firstTask != null) || 
        // (rs == SHUTDOWN && workQueue.isEmpty())
        // 1. 线程池状态大于SHUTDOWN时,直接返回false(shutdownnow等状态不创建任何worker,队列任务也是抛弃)
        // 2. 线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false(表示不接受新任务。firstTask为空有可能是因为核心worker设置为0新建的非核心worker,此时需要执行队列任务因此不能返回false)
        // 3. 线程池状态等于SHUTDOWN,且firstTask为null,且队列为空,直接返回false(队列为空不需要建空worker了)
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        // 内层自旋
        for (;;) {
            int wc = workerCountOf(c);
            // worker数量超过容量,直接返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 使用CAS的方式增加worker数量。(+1)
            // 若增加成功,则直接跳出外层循环进入到第二部分
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 否则,说明CAS失败,ctrl发生了变化已经不是c了,需要重新获取
            c = ctl.get();  // Re-read ctl
            // 线程池状态发生变化,对外层循环进行自旋
            if (runStateOf(c) != rs)
                continue retry;
            // 其他情况(有别的线程新建worker/扩缩容等),直接内层循环进行自旋即可
            // else CAS failed due to workerCount change; retry inner loop
        } 
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    // 真正的worker添加
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            // worker的添加必须是串行的,因此需要加锁
            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)) {
                    // worker已经调用过了start()方法,则不再创建worker
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // worker创建并添加到workers成功
                    workers.add(w);
                    // 更新`largestPoolSize`变量
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 启动worker线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        // worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

compareAndIncrementWorkerCount


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

addWorkerFailed

Worker类

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /** 这个就是worker持有的线程,也就是线程池中的线程 */   
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** 每一个线程执行的任务数量的计数器 */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        // Worker继承AQS,使用AQS实现独占锁,并且是不可重入的,构造Worker对象的时候,会把锁资源状态设置成-1,因为新增的线程,还没有处理过任务,是不允许被中断的
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 这儿是Worker的关键所在,使用了线程工厂(入参之一)创建了一个线程。传入的参数为当前worker
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }

    // Lock methods
    //    
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.

    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        // 独占锁,state只有0,1
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

// 如果正在执行任务,则不应该中断线程;
// 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断;
// 线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态
// 之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程
    void interruptIfStarted() {
        Thread t;
        // 如果state < 0表示初始化,不允许中断
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

worker其实就是一个Runable,其也是需要构造成一个Thread对象,然后调用Thread.start()方法运行的。只不过在worker的run方法中是定义了一个runWoker的方法。这个方法的主要内容从 for 循环不停的从task队列中获取对应的runable的task,然后同步调用这个task的run()方法。其实就是在某个线程中,不停的拿队列中的任务进行执行。

为什么给Worker要继承AQS而不是ReentrantLock

为什么不可重入:

如果继承Reentrant,可重入锁会导致主线程的扩缩容和shutdown操作会互相影响

为什么独占

Worker并没有判断lock或unlock的触发线程是不是当前持有锁的线程,因此我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程

小结

一般来说,interruptIdleWorkers()方法的调用不是在本工作线程,而是在主线程中调用的,还记得shutdown()和shutdownNow()方法吗?

观察两个方法中中断线程的方法,shutdown()中就是调用了interruptIdleWorkers()方法来中断线程地,而且interruptIdleWorkers()方法中就用到了tryLock(),只有获取到锁了才能中断线程,如果没有获取到锁则不中断。而调用tryLock()后没获取到锁只有一种原因,那就是lock()所在的地方runWorker()方法中,有任务正在执行。这样shutdown()方法就实现了只中断空闲线程,不会中断正在执行任务的线程。

而shutdownNow()中中断线程则很暴力,并没有tryLock(),而是直接中断了线程,所以调用shutdownNow()可能会中断正在执行的任务。

所以,Worker继承自AQS实际是要使用其锁的能力,这个锁主要是用来控制调用shutdown()时不要中断正在执行任务的线程。

那么为什么Worker使用AQS实现锁,而不直接用ReentrantLock呢?我们可以看到Worker的tryAcquire 方法,它是不允许重入的,而 ReentrantLock是允许重入的。所以这是为了实现不可重入的特性去反应线程现在的执行状态。

interruptIdleWorkers

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

setExclusiveOwnerThread


    /**
     * Sets the thread that currently owns exclusive access.
     * A {@code null} argument indicates that no thread owns access.
     * This method does not otherwise impose any synchronization or
     * {@code volatile} field accesses.
     * @param thread the owner thread
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

runWorker

这里要注意,run()方法是Worker类的方法,但是其调用的runWorker()方法是ThreadPoolExecutor中的方法,run方法若退出(getTask()返回null)说明worker生命走到头了,由JVM销毁

final void runWorker(Worker w) {
    // 获取当前线程,其实这个当前线程,就是worker对象持有的线程,从线程池中拿到的任务就是由这个线程执行的
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
   // 将AQS锁资源的状态由-1变成0,运行该线程进行中断 因为在创建的时候将state设为-1了,现在开始执行任务了,也就需要加锁了,所以要把state再重新变为0,这样在后面执行任务的时候才能用来加锁,保证任务在执行过程中不会出现并发异常
    // 解锁
    w.unlock(); // allow interrupts
    // 这个变量用于判断是否进入过自旋(while循环)
    boolean completedAbruptly = true;
    try {
        // 这儿是自旋
        // 1. 如果firstTask不为null,则执行firstTask;
        // 2. 如果firstTask为null,则调用getTask()从队列获取任务。
        // 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待
        while (task != null || (task = getTask()) != null) {
            // 这儿对worker进行加锁,是为了达到下面的目的
            // 1. 降低锁范围,提升性能
            // 2. 保证每个worker执行的任务是串行的
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            // 如果线程池正在停止,则对当前线程进行中断操作
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            // 执行任务,且在执行前后通过`beforeExecute()`和`afterExecute()`来扩展其功能。
            // 这两个方法在当前类里面为空实现。
            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 {
                // 帮助gc
                task = null;
                // 已完成任务数加一 
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        // 自旋操作被退出,说明线程池正在结束
        processWorkerExit(w, completedAbruptly);
    }
}

getTask

判断是否设置了超时退出的逻辑都在这里,正常是阻塞在queue的poll或者take或者自旋锁。当返回null时意味着worker设置有超时自毁需要结束了

// 返回任务Runnable
private Runnable getTask() {
    // timedOut表示 记录上一次从队列中获取任务是否超时
    boolean timedOut = false; // Did the last poll() time out?
    // 自旋
    for (;;) {
        // 这一部分是判断线程池状态
        // 获取线程池的状态和线程池中线程数量组成的整形字段,32位
        // 高3位代表线程池的状态,低29位代表线程池中线程的数量
        int c = ctl.get();
        // 获取高3位的值,即线程池的状态
        int rs = runStateOf(c);
        // 如果线程池状态不是Running状态,并且 线程也不是SHUTDOWN状态 或者任务队列已空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            // 则将线程池中的线程数量减1  就是说该线程已经不是运行状态了,所以要这个worker线程也没有用了,直接将该worker去掉。这个是原子操作
            decrementWorkerCount();
            //返回一个空任务,因为:
            // 1:如果任务队列已空,则想返回任务也没有
            // 2:如果线程池处于STOP或者之上的状态,则线程池不允许再处理任务
            return null;
        }
        // 这一部分是判断线程池有效线程数量
        // 获取低29位的值,即线程池中线程的数量
        int wc = workerCountOf(c);
        // timed是否需要进行超时控制
        // allowCoreThreadTimeOut默认false
        // 当线程池中线程的数量没有达到核心线程数量时,获取任务的时候允许超时  如果将allowCoreThreadTimeOut设为true,那也不允许超时
        // 当线程池中线程的数量超过核心线程数量时,获取任务的时候不允许超时   
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 这个很好理解
 
        // wc > maximumPoolSize的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
        // timed && timedOut 如果为true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
        // 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减1;
        // 如果减1失败,则continue返回重试
        // 如果wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
        if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
 
        // 如果上面都没问题,就可以获取任务了
        try {
            // 获取任务
            // 如果timed = true ,说明需要做超时控制,则根据keepAliveTime设置的时间内,阻塞等待从队列中获取任务
            // 如果timed = false,说明不需要做超时控制,则阻塞,直到从队列中获取到任务为止
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            // 如果获取到任务,则把任务返回
            if (r != null)
                return r;
            // 执行到这里,说明在允许的时间内,没有获取到任务
            timedOut = true;
        } catch (InterruptedException retry) {
            // 获取任务没有超时,但是出现异常了,将timedOut设置为false
            timedOut = false;
        }
    }
}

poll(timeout, unit)方法会在超时时返回null,如果timeout<=0,队列为空时直接返回null。

take()方法会一直阻塞直到取到任务或抛出中断异常。

allowCoreThreadTimeOut


public void allowCoreThreadTimeOut(boolean value) {
    if (value && keepAliveTime <= 0)
        throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
    if (value != allowCoreThreadTimeOut) {
        allowCoreThreadTimeOut = value;
        if (value)
            // 中断空闲的线程
            interruptIdleWorkers();
    }
}

processWorkerExit(不重要)

runWorker 的 while 循环执行完毕以后,在 finally 中会调用 processWorkerExit()方法,来销毁工作线程。该方法就是判断当前线程是需要将其删除还是继续执行任务。该方法也是ThreadPoolExecutor中的方法。但这个方法只有在一定情况下才会起作用,如果已经从队列中取不出任务来了,或者在worker执行任务过程中出现了错误,这个方法就起到了作用,如果正常的话这个方法就没啥用。
 

private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) {
    // 如果 completedAbruptly = true ,则线程执行任务的时候出现了异常,需要从线程池中减少一个线程
    // 如果 completedAbruptly = false,则执行getTask方法的时候已经减1,这里无需在进行减1操作
    if (completedAbruptly)
        decrementWorkerCount();
    
    // 获取线程池的锁,因为后面是线程池的操作,为了并发安全,需要对线程池加锁
    final ReentrantLock mainLock = this.mainLock;
    // 线程池加锁
    mainLock.lock();
    try {
        // 统计该线程池完成的任务数
        completedTaskCount += w.completedTasks;
        // 从线程池中移除一个工作线程    works是线程池持有的一个集合  
        workers.remove(w); // 将没用的worker去掉,也就是当前传入的worker
    } finally {
        // 线程池解锁
        mainLock.unlock();
    }
    // 根据线程池的状态,决定是否结束该线程池
    tryTerminate(); // 钩子方法
 
    // 判断线程池是否需要增加线程
    // 获取线程池的状态
    int c = ctl.get();
    // -当线程池是RUNNING或SHUTDOWN状态时
    // --如果worker是异常结束(即completedAbruptly为false),那么会直接addWorker;
    // ---如果allowCoreThreadTimeOut = true,并且等待队列有任务,至少保留一个worker;
    // ---如果allowCoreThreadTimeOut = false,活跃线程数不少于corePoolSize
    if (runStateLessThan(c, STOP)) { // 线程池状态小于STOP,就说明当前线程池是RUNNING或SHUTDOWN状态
        // 如果worker是异常结束的,不进入下面的分支,直接去addWorker
        if (!completedAbruptly) {
            // 根据allowCoreThreadTimeOut的值,来设置线程池中最少的活跃线程数是0还是corePoolSize
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果等待队列中有任务,要至少保留一个worker
            if (min == 0 && ! workQueue.isEmpty())
                // 至少保留一个worker
                min = 1;
            // 如果活跃线程数大于等于min,直接返回,不需要再调用addWorker来增加线程池中的线程了
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 增加线程池中的worker
        addWorker(null, false);
    }
}

shutdown

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    // 上锁,确保同一时间只能有一个线程执行此操作
    mainLock.lock();
    try {
        // 检查方法调用方是否用权限关闭线程池以及中断工作线程
        checkShutdownAccess();
        // 将线程池运行状态设置为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断所有空闲线程
        interruptIdleWorkers();
        // 此方法在ThreadPoolExecutor中是空实现,具体实现在其子类ScheduledThreadPoolExecutor
        // 中,用于取消延时任务。
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
  	// 尝试将线程池置为TERMINATED状态
    tryTerminate();
}

advanceRunState

private void advanceRunState(int targetState) {
    // 程序循环执行,直到将运行状态设为目标值
    for (;;) {
        // 获取AtomicInteger变量中的整数值
        int c = ctl.get();
        // if条件的前半部分是判断当前运行状态是否大于等于给定值
        if (runStateAtLeast(c, targetState) ||
            // 后半部分利用CAS操作将运行状态设置为目标值,成功的话会返回true,失败则返回false
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
     }
 }

tryTerminate

final void tryTerminate() {
  	// 依然是CAS自旋操作
    for (;;) {
      	// 获取ctl中的整型值
        int c = ctl.get();
      	// 如果是以下几种情况之一的,对应第一个if判断的几种状况,直接return,不错任何操作
      	// 1.线程池处于RUNNING状态
      	// 2.线程池处于TIDYING状态或TERMINATED状态
      	// 3.线程池处于SHUTDOWN状态但是任务队列不为空
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
      	// 经过上一个if判断的过滤,当前线程池除了线程数不为0,其他条件都已经满足关闭的要求
      	// 所以进行下一个if判断,如果线程数不为0,则中断一个空闲线程并返回
        if (workerCountOf(c) != 0) {
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
				// 方法进行到这里说明线程池所有条件都已经满足关闭的要求,下面的操作就是将线程池状态
      	// 置为TERMINATED
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
          	// 将线程池状态置为TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                  	// 本类中是空方法,子类有具体实现,用于进行关闭线程池的前置操作
                    terminated();
                } finally {
                  	// 将线程池置为TERMINATED状态,且线程数为0
                    ctl.set(ctlOf(TERMINATED, 0));
                  	// 唤醒其他正在等待的线程
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS,失败的话会继续循环执行CAS操作
    }
}

shutdownNow

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      	// 检查方法调用方是否用权限关闭线程池以及中断工作线程
        checkShutdownAccess();
      	// 将线程池运行状态置为STOP
        advanceRunState(STOP);
      	// 中断所有线程,包括正在运行的线程
        interruptWorkers();
      	// 将未执行的任务移入列表中
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
  	// 尝试将线程池置为TERMINATED状态
    tryTerminate();
    return tasks;
}

interuptWorkers

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
      	// 中断所有线程
        for (Worker w : workers)
            // 调用内部类Worker自身方法中断线程
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

关闭线程池的正确姿势,shutdown(), shutdownNow()和awaitTermination() 该怎么用? - 掘金 (juejin.cn)

Worker工作线程的生命周期

 Worker执行任务的模型如下图所示:

面试题

面试:假如提供一个闹钟服务,订阅这个服务的人特别多,10亿人,怎么优化?

1.任务分发到多台边缘服务器

2. 每台服务器用线程池+队列,一个个消费

ThreadPoolExecutor源码解析

线程池的生命周期

线程池有哪些状态?状态是如何转换的?_线程池的状态_CRMEB定制开发的博客-CSDN博客

线程池的状态有以下 5 种:

RUNNING:运行状态,线程池创建好之后就会进入此状态,如果不手动调用关闭方法,那么线程池在整个程序运行期间都是此状态。

SHUTDOWN:关闭状态,不再接受新任务提交,但是会将已保存在任务队列中的任务处理完。

STOP:停止状态,不再接受新任务提交,并且会中断当前正在执行的任务、放弃任务队列中已有的任务。

TIDYING:整理状态,所有的任务都执行完毕后(也包括任务队列中的任务执行完),当前线程池中的活动线程数降为 0 时的状态。到此状态之后,会调用线程池的 terminated() 方法。

TERMINATED:销毁状态,当执行完线程池的 terminated() 方法之后就会变为此状态。

这 5 种状态可以在 ThreadPoolExecutor 源码中找到,如下图所示:

线程池状态转移

线程池的状态转移有两条路径:

当调用 shutdown() 方法时,线程池的状态会从 RUNNING 到 SHUTDOWN,再到 TIDYING,最后到 TERMENATED 销毁状态。

当调用 shutdownNow() 方法时,线程池的状态会从 RUNNING 到 STOP,再到 TIDYING,最后到 TERMENATED 销毁状态。

terminated方法

线程池中的 terminated() 方法,也就是线程池从 TIDYING 转换到 TERMINATED 状态时调用的方法,默认是空的,它的源码如下:

我们可以在创建线程池的时候重写 terminated() 方法

流式API的ForkJoinPool的算法实现

流式API的底层也是使用 ForkJoinPool 来实现的。nums.parallelStream().forEach这种并行流处理起来效率会更高一些。

package com.mashibing.juc.c_026_01_ThreadPool;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class T13_ParallelStreamAPI {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>();
        Random r = new Random();
        for (int i = 0; i < 10000; i++) nums.add(1000000 + r.nextInt(1000000));

        //System.out.println(nums);

        long start = System.currentTimeMillis();
        nums.forEach(v -> isPrime(v));
        long end = System.currentTimeMillis();
        System.out.println(end - start);

        //使用parallel stream api

        start = System.currentTimeMillis();
        nums.parallelStream().forEach(T13_ParallelStreamAPI::isPrime);
        end = System.currentTimeMillis();

        System.out.println(end - start);
    }

    static boolean isPrime(int num) {
        for (int i = 2; i <= num / 2; i++) {
            if (num % i == 0) return false;
        }
        return true;
    }
}

Disruptor

DisruptorD多线程与高并发(九):单机压测工具JMH,单机最快MQ - Disruptor原理解析_寒泉Hq的博客-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值