java并发编程之Executor线程池

落爺英雄遲暮 提交于 2020-02-10 10:55:01

java并发编程之Executor线程池

1 线程&多线程&进程

a、线程:
线程是进程的一个实体,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

b、多线程:
多线程指在单个程序中可以同时运行多个不同的线程执行不同的任务。多线程编程的目的,其实就是“最大限度地利用 cpu 资源”,当某一线程的处理不需要占用 cpu 而只和 io 等资源打交道时,让需要占用 Cpu 的其他线程有其他机会获得 cpu 资源。从根本上说,这就是多线程编程的最终目的。

c、线程与进程的区别

  • 进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
  • 一个程序至少有一个进程,一个进程至少有一个线程。
2 线程的实现方式
  • 继承Thread类,重写run方法
public class ThreadDemo extends Thread {
    
    private static final Logger logger = LoggerFactory.getLogger(ThreadDemo.class);

    public static void main(String[] args) {
        ThreadDemo thread =  new ThreadDemo();
        thread.start();
    }

    @Override
    public void run() {
        logger.info("山川异域,风月同舟");
    }
}

  • 实现Runnable接口,重写run方法·
    public class RunnableDemo implements Runnable{
    private static final Logger logger = LoggerFactory.getLogger(RunnableDemo.class);
    
    public static void main(String[] args) {
        new Thread(new RunnableDemo()).start();
    }
    
    @Override
    public void run() {
        logger.info("山川异域,风月同舟");
    }
    

}
```

  • 通过Callable和FutureTask创建线程,并实现其call()方法
public class CallableDemo implements Callable<String> {
    private static final Logger logger = LoggerFactory.getLogger(CallableDemo.class);
    
    @Override
    public String call() throws Exception {
        return "山川异域";
    }

    public static void main(String[] args) throws InterruptedException,ExecutionException {
        FutureTask task = new FutureTask(new CallableDemo());
        new Thread(task).start();
        logger.info(task.get().toString());
    }
}
3 三种实现线程的区别

实现 Runnable 接口相比继承 Thread 类有如下优势

  • 可以避免由于 Java 的单继承特性而带来的局限
  • 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
  • 线程池只能放入实现 Runable 或 Callable 类线程,不能直接放入继承 Thread 的类
    实现 Runnable 接口和实现 Callable 接口的区别
  • Runnable 是自从 java1.1 就有了,而 Callable 是 1.5 之后才加上去的
  • 实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable 接口的任务线程不能返回结果
  • Callable 接口的 call()方法允许抛出异常,而 Runnable 接口的 run()方法的异常只能在内部消化,不能继续上抛
  • 加入线程池运行,Runnable 使用 ExecutorService 的 execute 方法,Callable 使用 submit 方法
    注: Callable 接口支持返回执行结果,此时需要调用 FutureTask.get()方法实现,此方法会阻塞主线程直到获取返回结果,当不调用此方法时,主线程不会阻塞
4 线程的生命周期&状态

在这里插入图片描述

状态 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,JAVA线程将操作系统中的就绪和运行两种状态统称为’运行中’
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定时间自行返回的
TERMINATED 终止状态,表示当前线程已经执行完毕
5 tips 查看线程状态:

jps -> jstack pid (常用于判断死锁)

6 什么是Executor框架?
  • Executor的内部实现主要就是线程池来实现,既Executor是通过线程池的方式来控制上层的调度的。所以Executor一定角度上扮演者线程工厂的角色,我们可以通过Executor框架创建特定功能的线程池。
7 为什么引入Executor框架?
  • 每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的。
  • 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
  • 使用new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现,所以为了解决这一个问题,JDK1.5开始引入了Executor框架用于管理线程和调度线程,可以参考Executor框架的两级调度模型
8 Executor框架的两级调度模型

在HotSpot 虚拟机线程模型中,Java线程(java.lang.Thread)被一对一的映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程。但该Java线程终止时,这个本地操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU.
而有了Executor框架之后,我们可以将线程调度分为上下两层:

  • 上层是Java层面上的应用线程层
  • 下层是本地操作系统层面上的底层线程层
    在这里插入图片描述应用程序通过Executor框架来控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
9 线程池框架

在这里插入图片描述

  • 第一层结构

    • sun.nio.ch.AsynchronousChannelGroupImpl(Iocp) 异步channel –AIO相关实现

    • java.util.concurrent.CompletableFuture.ThreadPerTaskExecutor (启动一个线程执行)

    • sun.net.httpserver.ServerImpl.DefaultExecutor (more执行器,直接执行)

    • com.sun.jmx.remote.internal.ClientNotifForwarder.LinearExecutor (线性执行器)
      java.util.concurrent.ExecutorService (核心执行器服务)

  • 接口简介

    • java.util.concurrent.Executor (执行器,执行方法)
    • java.util.concurrent.ExecutorService (执行服务) 包含服务的生命周期
    • java.util.concurrent.ScheduledExecutorService (调度相关的服务)
  • 核心实现类

    • java.util.concurrent.ThreadPoolExecutor (普通的的线程池实现类)
    • java.util.concurrent.ScheduledThreadPoolExecutor (调度的核心实现类)
  • 辅助类

    • java.util.concurrent.Executors
  • 完成服务

    • java.util.concurrent.CompletionService
    • java.util.concurrent.ExecutorCompletionService
10 线程池执行原理
  • ThreadPoolExecutor参数最全的构造方法:
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

• corePoolSize :池中所保存的线程数,包括空闲线程
• maximumPoolSize:池中允许的最大线程数
• keepAliveTime: 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
• unit:keepAliveTime 参数的时间单位
• workQueue :执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务
• threadFactory:执行程序创建新线程时使用的工厂
• handler :由于超出线程范围和队列容量而使执行

  • 线程池运行思路
    • 如果当前池大小 poolSize 小于 corePoolSize ,则创建新线程执行任务
    • 如果当前池大小 poolSize 大于 corePoolSize ,且等待队列未满,则进入等待队列
    • 如果当前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待队列已满,则创建新线程
      执行任务
    • 如果当前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待队列已满,则调用拒绝策
      略来处理该任务
    • 线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,
    • 如果在 keepAliveTime 里等不到新的任务了,那么线程就会退出
  • addWorker的方法实现 java.util.concurrent.ThreadPoolExecutor#addWorker
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;// 两种情况1.如果非运行状态  2.不是这种情况(停止状态并且是null对象并且workQueue不等于null)

    for (;;) {
        int wc = workerCountOf(c);
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;// 判断是否饱和容量了
        if (compareAndIncrementWorkerCount(c)) //增加一个work数量 然后跳出去
            break retry;
        c = ctl.get();  // Re-read ctl  增加work失败后继续递归
        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);//增加一个worker
    final Thread t = w.thread;
    if (t != null) {//判断是否 为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);   //增加work
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        if (workerAdded) { //本次要是新增加work成功就调用start运行
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}
return workerStarted;

  • java.util.concurrent.ThreadPoolExecutor#execute
if (command == null)
    throw new NullPointerException();
int c = ctl.get();
//判断是否小于核心数量,是直接新增work成功后直接退出 
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    c = ctl.get();// 增加失败后继续获取标记
}
//判断是运行状态并且扔到workQueue里成功后
if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
//再次check判断运行状态如果是非运行状态就移除出去&reject掉
    if (! isRunning(recheck) && remove(command))
        reject(command);
    else if (workerCountOf(recheck) == 0) //否则发现可能运行线程数是0那么增加一个null的worker。
        addWorker(null, false);
}
else if (!addWorker(command, false)) //直接增加worker如果不成功直接reject
    reject(command);

  • java.util.concurrent.ThreadPoolExecutor#addWorker
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;// 两种情况1.如果非运行状态  2.不是这种情况(停止状态并且是null对象并且workQueue不等于null)

    for (;;) {
        int wc = workerCountOf(c);
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;// 判断是否饱和容量了
        if (compareAndIncrementWorkerCount(c)) //增加一个work数量 然后跳出去
            break retry;
        c = ctl.get();  // Re-read ctl  增加work失败后继续递归
        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);//增加一个worker
    final Thread t = w.thread;
    if (t != null) {//判断是否 为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);   //增加work
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                workerAdded = true;
            }
        } finally {
            mainLock.unlock();
        }
        if (workerAdded) { //本次要是新增加work成功就调用start运行
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}
return workerStarted;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!