Java多线程学习-1

目录

什么是进程

什么是程序呢?

进程与程序是什么关系呢?

进程基本原理

什么是线程

线程基本原理

栈内存的作用

 线程原理

Thread类详解

线程核心原理

线程生命周期

线程的基本操作

线程sleep操作

线程interrupt操作

线程join操作

线程yield方法

线程的daemon操作

创建线程的方法

继承Thread类

实现Runnable接口

使用Callable和FutureTask创建线程

Callable接口

RunnableFuture接口

Future接口

FutureTask类

线程池

线程池架构

 线程池的标准创建方式

线程池状态

优雅关闭线程池

调度器的钩子方法

确定线程池的线程数

ThreadPoolExecutor源码

关键的属性

内部状态

任务的执行

任务的提交

FutureTask对象


多线程是Java程序运行的基础机制,对于高性能、高并发Java程序来说用好多线程尤为重要,可以说多线程知识是每个Java工程师必备的知识。本文章的目标是由浅入深,对Java多线程的核心原理做一个详尽的介绍。

进程和线程是操作系统中两个容易混淆的概念,那我们有必要区分一下,什么是进程,什么线程。

什么是进程

简单来说,进程是程序的一次启动执行。

什么是程序呢?

程序是存放在硬盘中的可执行文件,主要包括代码指令和数据。

一个进程是一个程序的一次启动和执行,是操作系统将程序装入内存,给程序分配必要的系统资源,并且开始运行程序的指令。

进程与程序是什么关系呢?

同一个程序可以多次启动,每启动一次就对应一个或者多个进程。

日常我们跟计算机打交道的时候,最常接触的就是一些应用程序,比如 Word、浏览器,我们可以直观感受到它们的存在。

其实,在我们启用 Word 这些应用时,操作系统在背后就会建立至少一个进程。

在linux的终端输入ps命令,我们就可以看到系统中有多少个进程了。

进程基本原理

一般来说,一个进程由程序段,数据段和进程控制块三部分组成。

用脑图描述:

 对应日常开发来说,JVM运行着用Java编写的程序,每当启动一个应用程序时,就会启动一个JVM进程,而进程内部,代码是以一个或者多个线程来运行的;程序的入口就是main方法,点击运行就产生了一个线程(主线程);

什么是线程

什么是线程?

  • 线程是CPU调度的最小单位
  • 一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源
  • 进程仍然是操作系统资源分配的最小单位

Java程序就是标准的多线程执行过程,每当运行一个class类实际上就是启动了一个JVM进程,理论上该进程内部至少会启动两个(实际上远不止)线程,一个是main线程,一个是GC(垃圾回收)线程。

线程基本原理

一个标准的线程主要由三部分组成,即线程描述信息、程序计数器(ProgramCounter,PC)和栈内存。

线程脑图

栈内存的作用

存放方法帧的内存也被叫作栈内存。

那什么是方法帧?

Java比较熟知的单位是“方法”,那方法的每一次执行都需要为其分配一个栈帧(方法帧),主要用来保存该方法的局部变量,方法的返回地址以及其他方法的相关信息。

Java方法有两种返回函数的方式,一种是正常的函数返回,使用 return 指令,另一种是抛出异常,不管用哪种方式,都会导致栈帧被弹出。

栈帧的脑图


 线程原理

Thread类详解

线程核心原理

操作系统提供了强大的线程管理能力,因此Java不需要进行独立的线程管理和调度,而是将调度工作委托给操作系统。

由于CPU计算频率非常高,每秒以十亿为单位,因此可以将其从毫秒的维度进行分段,每一小段就是我们说的CPU时间片了。目前操作系统主流是 基于CPU时间片进行线程调度,即只有得到CPU时间片才能执行指令,没有就处于就绪状态,等待系统分配下一个CPU时间片。由于时间片非常短,所以看着像很多线程同时在进行。

而调度模型则分为:分时调度和抢占式调度。

  • 分时调度 就是所有线程轮流占用CPU,每个线程都平等
  • 抢占式则是系统按照线程优先级分配CPU时间片,不过也并不会完全按照优先级,有时候优先很低但是仍然能抢占到时间片。

线程生命周期

Java中线程生命周期有六种。

Thread.State是一个内部枚举类,定义了6个枚举常量,分别代表Java线程的6种状态。

线程生命周期脑图如下

线程的基本操作

Java线程的常用操作基本都定义在Thread类中

线程sleep操作

sleep的作用是让目前正在执行的线程休眠,让CPU去执行其他的任务。

即 线程状态 从 Runnable -> TIMED_WAITING(sleeping)状态。

当睡眠时间满后,线程会变成就绪状态,等待再次获得时间片。

线程interrupt操作

Java提供了stop()方法,用于终止正在运行的线程,但是这个方法官方不推荐大家使用。

究其原因,就是因为使用这个方法很危险,就像开满了应用的计算机,突然关闭电源,所有数据都来不及保存。线程也一样,有可能持有某把锁,强行中断会可能导致锁不能释放;或者是在操作数据库,可能导致数据不一致;

那有小伙伴会问,那如何实现优雅地停止线程呢?

Thread的interrupt(),配合InterruptedException,isInterrupted()来实现优雅地停止线程。

  1. 如果线程处于阻塞状态(BLOCKED,WAITING,TIMED_WAITING),就会马上退出阻塞,并抛出InterruptedException异常,线程可以通过捕获这个异常做一定的处理,然后让线程退出。
  2. 如果线程处于运行状态,当收到该指令,线程不收任何影响继续运行,仅仅是线程的中断标记被设置为true。程序可以在适当的位置通过调用isInterrupted()方法来查看自己是否被中断,并执行退出操作

说明:如果收到了中断继续调用阻塞方法,则不再触发中断异常。

线程join操作

join()方法是Thread类的一个实例方法,有三个重载版本:


//版本1,此方法会把当前线程状态变成TIMED_WAITING,直到被合并线程执行结束
public final void join() throws InterruptedException;

/**
* 版本2,此方法会把当前线程状态变成TIMED_WAITING,直到被合并线程执行结束,
* 或者等待被合并线程执行var1的时间
*/
public final synchronized void join(long var1) throws InterruptedException;

/**
* 版本3,此方法会把当前线程状态变成TIMED_WAITING,直到被合并线程执行结束,
* 或者等待被合并线程执行var1+var3的时间
*/
public final synchronized void join(long var1, int var3) throws InterruptedException;

简单来说是线程合并。但是合并有点难说清,举个例子来说好了。

public class ThreadB extends Thread{

    public ThreadB(String name){
        super(name);
    }
    public ThreadB(){
        super();
    }

    @Override
    public void run() {
        System.out.println("线程B开始执行");
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程B结束执行");
    }
}

public class ThreadA extends Thread {

    private ThreadB threadB;

    public ThreadA(ThreadB threadB) {
        this.threadB = threadB;
    }

    public ThreadA(String name) {
        super(name);
    }

    public ThreadA() {
        super();
    }


    @Override
    public void run() {
        System.out.println("线程A执行开始执行");
        try {
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程A执行结束执行");
    }
}

public class ThreadLearn {

    public static void main(String[] args) throws InterruptedException {
        ThreadB b = new ThreadB();
        ThreadA a = new ThreadA(b);

        a.start();
        b.start();


    }
}

结果:

线程A执行开始执行
线程B开始执行
线程B结束执行
线程A执行结束执行

线程yield方法

让目前正在执行的线程放弃当前的执行,让出CPU时间片,使得CPU去执行其他线程。

处于让步的线程 状态还是 RUNNABLE,只是是属于就绪状态,需要等到时间片就可以运行了。

线程调用yield方法后,操作系统在重新进行线程调度时偏向于将执行机会让给优先级较高的线程。

总结一下:yield有一下特点:

  1. yield仅能使一个线程从运行状态转到就绪状态,而不是阻塞状态
  2. yield不能保证使得当前正在运行的线程迅速转换到就绪状态
  3. 调用之后,系统通过线程调度机制从所有就绪线程中挑选一个

线程的daemon操作

Java中的线程分为两类:守护线程与用户线程。

守护线程也称为后台线程,专门指在程序进程运行过程中,在后台提供某种通用服务的线程。比如,每启动一个JVM进程,都会在后台运行一系列的GC(垃圾回收)线程,这些GC线程就是守护线程,提供幕后的垃圾回收服务。

守护线程存在被JVM强行终止的风险,所以在守护线程中尽量不去访问系统资源。

在守护线程中创建的线程,新的线程都是守护线程。

创建线程的方法

继承Thread类

比较简单:

  1. 需要继承Thread类,创建一个新的线程类
  2. 需要继承Thread类,创建一个新的线程类
  3. 调用start()方法

实现Runnable接口

在Thread类的run()方法中,如果target(执行目标)不为空,就执行target属性的run()方法。

而target属性是Thread类的一个实例属性,并且target属性的类型为Runnable。

认识一下Runnable接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

而Thread 继承了Runnable接口,实现了run方法。

但是用start方法才是启动线程,实现了多线程。来看下过程:

start0是native方法

Thread还有一个registerNatives方法

在jvm.cpp中

这里 JVM_ENTRY 是一个宏,用来定义 JVM_StartThread 函数,可以看到函数内创建了真正的平台相关的本地线程,其线程函数是 thread_entry,如下:

 可以看到调用了 vmSymbols::run_method_name 方法,而 run_method_name 是在 vmSymbols.hpp 用宏定义的:

 新建类继承Thread,而Thread实现Runnable,会实现run方法,所以这里就是调用新建类的run方法。

使用Callable和FutureTask创建线程

之前创建线程的两种方式:通过Thread和Runnable方式,都有一个共同的缺点,就是不能获取异步执行的结果。

为了解决这个比较严重的问题,java1.5之后:通过 Callable和FutureTask类结合创建线程

Callable接口

package java.util.concurrent;


@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable接口类似于Runnable,区别在于 Runnable的唯一抽象方法run()没有返回值,也没有受检异常的异常声明,而Callable接口的call()有返回值,并且声明了受检异常,其功能更强大一些。

我们知道,Thread的target属性的类型为Runnable,而Callable接口与Runnable接口之间没有任何继承关系,那么该如何使用Callable接口创建线程呢?

好问题!那么该如何使用Callable接口创建线程呢?一个在Callable接口与Thread线程之间起到搭桥作用的重要接口:RunnableFuture接口准备登场。

RunnableFuture接口

package java.util.concurrent;

/**
 * A {@link Future} that is {@link Runnable}. Successful execution of
 * the {@code run} method causes completion of the {@code Future}
 * and allows access to its results.
 * @see FutureTask
 * @see Executor
 * @since 1.6
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

通过源码可以看到RunnableFuture接口实现了两个目标:

  1. 可以作为Thread线程实例的target实例
  2. 可以获取异步执行的结果

可以看出RunnableFuture起了牵线搭桥的作用,既和Runnable有联系,也和Future关联着;既继承了Runnable接口,从而保证了其实例可以作为Thread线程实例的target目标;同时,RunnableFuture通过继承Future接口,保证了可以获取未来的异步执行结果。

那Future接口有啥作用呢?

Future接口

package java.util.concurrent;

public interface Future<V> {

    /**
     * 取消异步任务的执行
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 获取异步任务的取消状态。如果任务完成前被取消,就返回true。
     */
    boolean isCancelled();

    /**
     * 获取异步任务的执行状态。如果任务执行结束,就返回true。
     */
    boolean isDone();

    /**
     * 获取异步任务执行的结果.
     * 这个方法的调用是阻塞性的.如果异步任务没有执行完成,
     * 异步结果获取线程(调用线程)会一直被阻塞,直到其异步结果返回给调用线程
     */
    V get() throws InterruptedException, ExecutionException;


    /**
     * 设置时限,阻塞性地获取异步任务执行的结果
     * 如果其阻塞时间超过设定的timeout时间,该方法将抛出异常,调用线程可捕获此异常
     */
    V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

Future是一个对异步任务进行交互、操作的接口,不过Future仅仅是一个接口,通过它没有办法直接完成对异步任务的操作,FutureTask是Future接口默认的实现类。

FutureTask类

FutureTask类是Future接口的实现类,提供了对异步任务的操作的具体实现。

不仅实现了Future接口,还实现了Runnable接口,或者更加准确地说,FutureTask类实现了RunnableFuture接口。

 所以说,FutureTask类才是真正的在Thread与Callable之间搭桥的类。

FutureTask如何完成多线程的并发执行、任务结果的异步获取呢?

有一个Callable类型的成员,callable实例属性用来保存并发执行的Callable<V>类型的任务,并且callable实例属性需要在FutureTask实例构造时进行初始化

/** The underlying callable; nulled out after running */
    private Callable<V> callable;

还有一个重要的成员变量:outcome

/** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes

FutureTask的outcome实例属性用于保存callable成员call()方法的异步执行结果。

在FutureTask类的run()方法完成callable成员的call()方法的执行之后,其结果将被保存在outcome实例属性中,供FutureTask类的get()方法获取。

看一个案例:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

/**
 * @Author: DonaldCen
 * @Date: 2021/10/19 11:52
 * @Desc:
 */
@Slf4j
public class FutureTaskTest {
    public static final int SLEEP_GAP = 5000;

    public static String getCurThreadName() {
        return Thread.currentThread().getName();
    }

    static Callable<Boolean> hotWaterJob = new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            try {
                log.info("洗好水壶");
                log.info("灌上凉水");
                log.info("放在火上");
                Thread.sleep(SLEEP_GAP);
                log.info("水开了");
            } catch (InterruptedException e) {
                log.info("发生异常被中断");
                return false;
            }
            log.info("运行结束");
            return true;
        }
    };

    static Callable<Boolean> washJob = new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            try {
                log.info("洗茶壶");
                log.info("洗茶杯");
                log.info("拿茶叶");
                Thread.sleep(SLEEP_GAP);
                log.info("洗完了");
            } catch (InterruptedException e) {
                log.info("发生异常被中断");
                return false;
            }
            log.info("清洗工作 运行结束");
            return true;
        }
    };

    public static void drinkTea(boolean waterOk, boolean cupOk) {
        if (waterOk && cupOk) {
            log.info("泡茶喝");
        } else if (!waterOk) {
            log.info("烧水失败,没有茶喝了");
        } else if (!cupOk) {
            log.info("杯子洗不了,没有茶喝了");
        } else {
            log.info("烧水失败,杯子也洗不了");
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        FutureTask<Boolean> hotWaterTask = new FutureTask<>(hotWaterJob);
        Thread h = new Thread(hotWaterTask,"** 烧水-Thread");

        FutureTask<Boolean> washTask = new FutureTask<>(washJob);
        Thread w = new Thread(washTask, "** 清洗-Thread");

        h.start();
        w.start();

        try {
            Boolean waterOk = hotWaterTask.get();
            Boolean cupOk = washTask.get();
            drinkTea(waterOk,cupOk);
        } catch (InterruptedException e) {
            log.info(getCurThreadName() + "发生中断异常被中断");
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        log.info(getCurThreadName() + "运行结束。");
    }
}

总结一下步骤:

  1. 创建一个Callable接口的实现类(无论是类继承还是直接new Callable,本质都是Callable接口的实现类),并实现其call()方法,实现其异步执行的业务逻辑,且可以具有返回值。
  2. 使用Callable实现类的实例构造一个FutureTask实例
  3. 使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例。
  4. 调用Thread实例的start()方法启动新线程,启动新线程的run()方法并发执行,原理是启动Thread实例的run()方法并发执行后,会执行FutureTask实例的run()方法,最终会并发执行Callable实现类的call()方法
  5. 调用FutureTask对象的get()方法阻塞性地获得并发线程的执行结果

线程池

之前所说的创建线程方法,都是用完都销毁了,不能复用。

实际上创建一个线程实例在时间成本、资源耗费上都很高,在高并发的场景中,不能频繁进行线程实例的创建与销毁,而是需要对已经创建好的线程实例进行复用,这就涉及线程池的技术。

Java线程的创建非常昂贵,需要JVM和OS(操作系统)配合完成大量的工作:

  1. 必须为线程堆栈分配和初始化大量内存块,其中包含至少1MB的栈内存
  2. 需要进行系统调用,以便在OS(操作系统)中创建和注册本地线程

线程池架构

 线程池的标准创建方式

ThreadPoolExecutor构造方法有多个重载版本,其中一个比较重要的构造器如下:

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

这些参数都比较重要,总结一下:

线程池的corePoolSize (核心线程数)和maximumPoolSize (最大线程数)有以下规则:

  1. 当在线程池接收到新任务,并且当前工作线程数少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求,直到线程数达到corePoolSize。
  2. 如果当前工作线程数多于corePoolSize数量,但小于maximumPoolSize数量,且阻塞队列没满,则把任务存放到阻塞队列中。如果阻塞队列放满了,则开始新建线程。
  3. 当线程池里面的线程数等于最大线程数,如果这时候再来任务,则启动拒绝策略,根据策略开始丢弃相关任务。
  4. 通过设置corePoolSize和maximumPoolSize相同,可以创建一个固定大小的线程池
  5. 当maximumPoolSize被设置为无界值(如Integer.MAX_VALUE)时,线程池可以接收任意数量的并发任务。

向线程池提交任务有两种方式

  1. 调用execute()方法
  2. 调用submit()方法

两种方式的区别:

  1. 两种方式接受的参数不一样
    1. Execute()方法只能接收Runnable类型的参数,而submit()方法可以接收Callable、Runnable两种类型的参数
    2. Callable类型的任务是可以返回执行结果的,而Runnable类型的任务不可以返回执行结果;Runnable不允许抛出异常,Callable允许抛出异常
  2. submit()提交任务后会有返回值,而execute()没有
  3. submit()方便Exception处理

线程池状态

优雅关闭线程池

 

调度器的钩子方法

ThreadPoolExecutor类提供了三个钩子方法(空方法),这三个钩子方法一般用作被子类重写,具体如下:

    /**
     * 任务执行之前的钩子方法(前钩子)
     */
    protected void beforeExecute(Thread var1, Runnable var2) {
    }

    /**
     * 任务执行之后的钩子方法(后钩子)
     */
    protected void afterExecute(Runnable var1, Throwable var2) {
    }

    /**
     * 线程池终止时的钩子方法(停止钩子)
     */
    protected void terminated() {
    }

确定线程池的线程数

为 IO密集型任务确定线程数

由于IO密集型任务的CPU使用率较低,导致线程空余时间很多,因此通常需要开CPU核心数两倍的线程。当IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。

  1. 为参考的IO线程池调用了allowCoreThreadTimeOut(…)方法,并且传入了参数true,则keepAliveTime参数所设置的Idle超时策略也将被应用于核心线程,当池中的线程长时间空闲时,可以自行销毁。
  2. 使用有界队列缓冲任务而不是无界队列,如果128太小,可以根据具体需要进行增大,但是不能使用无界队列
  3. corePoolSize和maximumPoolSize保持一致,使得在接收到新任务时,如果没有空闲工作线程,就优先创建新的线程去执行新任务,而不是优先加入阻塞队列,等待现有工作线程空闲后再执行
  4. 使用懒汉式单例模式创建线程池,如果代码没有用到此线程池,就不会立即创建。
  5. 使用JVM关闭时的钩子函数优雅地自动关闭线程池

为cpu密集型任务确定线程数

CPU密集型任务也叫计算密集型任务,其特点是要进行大量计算而需要消耗CPU资源。

所以要最高效地利用CPU,CPU密集型任务并行执行的数量应当等于CPU的核心数。

比如4个核心的CPU,通过4个线程并行地执行4个CPU密集型任务,此时的效率是最高的。但是如果线程数远远超出CPU核心数量,就需要频繁地切换线程,线程上下文切换时需要消耗时间,反而会使得任务效率下降。因此,对于CPU密集型的任务来说,线程数等于CPU数就行。

为混合型任务确定线程数

混合型任务既要执行逻辑计算,又要进行大量非CPU耗时操作(如RPC调用、数据库访问、网络通信等),所以混合型任务CPU的利用率不是太高,非CPU耗时往往是CPU耗时的数倍。

在为混合型任务创建线程池时,如何确定线程数呢?业界有一个比较成熟的估算公式,具体如下:

最佳线程数 = (线程等待时间与线程CPU时间之比+1)*CPU核数

通过公式可以看出:等待时间所占的比例越高,需要的线程就越多;CPU耗时所占的比例越高,需要的线程就越少。

面举一个例子:比如在Web服务器处理HTTP请求时,假设平均线程CPU运行时间为100毫秒,而线程等待时间(比如包括DB操作、RPC操作、缓存操作等)为900毫秒,如果CPU核数为8,那么根据上面这个公式,估算如下:

(900ms+100ms)/ 100ms * 8 = 80

ThreadPoolExecutor源码

关键的属性

//这个属性是用来存放 当前运行的worker数量以及线程池状态的
//int是32位的,这里把int的高3位拿来充当线程池状态的标志位,后29位拿来充当当前运行worker的数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//存放任务的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//worker的集合,用set来存放
private final HashSet<Worker> workers = new HashSet<Worker>();
//历史达到的worker数最大值
private int largestPoolSize;
//当队列满了并且worker的数量达到maxSize的时候,执行具体的拒绝策略
private volatile RejectedExecutionHandler handler;
//超出coreSize的worker的生存时间
private volatile long keepAliveTime;
//常驻worker的数量
private volatile int corePoolSize;
//最大worker的数量,一般当workQueue满了才会用到这个参数
private volatile int maximumPoolSize;

内部状态

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; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

其中AtomicInteger变量ctl的功能非常强大: 利用低29位表示线程池中线程数,通过高3位表示线程池的运行状态:

  • RUNNING: -1 << COUNT_BITS,即高3位为111,该状态的线程池会接收新任务,并处理阻塞队列中的任务
  • SHUTDOWN: 0 << COUNT_BITS,即高3位为000,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务
  • STOP : 1 << COUNT_BITS,即高3位为001,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • TIDYING : 2 << COUNT_BITS,即高3位为010, 所有的任务都已经终止;
  • TERMINATED: 3 << COUNT_BITS,即高3位为011, terminated()方法已经执行完成

任务的执行

execute –> addWorker –>runworker (getTask)

execute

ThreadPoolExecutor.execute(task)实现了Executor.execute(task)

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) {  
    //workerCountOf获取线程池的当前线程数;小于corePoolSize,执行addWorker创建新线程执行command任务
       if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // double check: c, recheck
    // 线程池处于RUNNING状态,把提交的任务成功放入阻塞队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // recheck and if necessary 回滚到入队操作前,即倘若线程池shutdown状态,就remove(command)
        //如果线程池没有RUNNING,成功从阻塞队列中删除任务,执行reject方法处理任务
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //线程池处于running状态,但是没有线程,则创建线程
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 往线程池中创建新的线程失败,则reject任务
    else if (!addWorker(command, false))
        reject(command);
}
  1. ctl 是 用来表示 线程池的状态与包含的线程数量的,是通过高位与低位实现的。
  2. 首先判断当前工作的线程数是否大于corePoolSize(核心线程数),如果小于,直接新增线程,通过调用addWorker方法,并且以corePoolSize为边界(即addWorker方法第二个参数为true,为false则是最大线程数为边界)
  3. 如果线程池状态是RUNNING并且顺利把任务提交到阻塞队列workQueue中,再次检查一下线程池的状态,如果线程池被关闭了,就把任务移除,并调用拒绝策略,拒绝任务;否则检查一下是否还有空闲线程,没有就再创建一个,这个时候,调用addWorker方法是 第一个参数为null,第二个参数为false
  4. 在线程池创建新的线程失败的话,就拒绝任务

addWorker方法

private final ReentrantLock mainLock = new ReentrantLock();

private boolean addWorker(Runnable firstTask, boolean core) {
    // CAS更新线程池数量
    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();  // 线程启动,执行任务(Worker.thread(firstTask).start());
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

addWorker方法的功能主要是  创建新的线程并执行任务。

而最主要的步骤是:

  1. 使用提交的任务firstTask作为参数传入Worker的构造方法,构建Worker对象
  2. Worker对象继承了AQS类,可以方便的实现工作线程的中止操作
  3. Worker对象同时实现了Runnable接口,可以将自身作为一个任务在工作线程中执行;同时run方法中 调用了runWorker(this);方法,把自身当做参数,传递给了runWorker方法

来仔细研究一下runWorker方法,这个方法是线程池的核心:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                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();
                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);
        }
    }

步骤:

  1. 线程启动之后,通过unlock方法释放锁,设置AQS的state为0,表示运行可中断
  2. Worker执行firstTask或从workQueue中获取任务
    1. 进行加锁操作,保证thread不被其他线程中断(除非线程池被中断)
    2. 检查线程池状态,倘若线程池处于中断状态,当前线程将中断
    3. 执行beforeExecute
    4. 执行任务的run方法
    5. 执行afterExecute方法
    6. 解锁操作

可以看到有一个 getTask()方法:

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

allowCoreThreadTimeOut为false,线程即使空闲也不会被销毁;倘若为ture,在keepAliveTime内仍空闲则会被销毁。

如果线程允许空闲等待而不被销毁timed == false,workQueue.take任务: 如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务,并执行;

如果线程不允许无休止空闲timed == true, workQueue.poll任务: 如果在keepAliveTime时间内,阻塞队列还是没有任务,则返回null;

任务的提交

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

任务的提交,是把Callable封装为RunnableFuture,然后再调用execute方法的(即上面的任务的执行)

执行FutureTask类的get方法时,会把主线程封装成WaitNode节点并保存在waiters链表中, 并阻塞等待运行结果;

FutureTask任务执行完成后,通过UNSAFE设置waiters相应的waitNode为null,并通过LockSupport类unpark方法唤醒主线程;

FutureTask对象

get()方法

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }


    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }

步骤:

  1. 如果主线程被中断,则抛出中断异常;
  2. 判断FutureTask当前的state,如果大于COMPLETING,说明任务已经执行完成,则直接返回;
  3. 如果当前state等于COMPLETING,说明任务已经执行完,这时主线程只需通过yield方法让出cpu资源,等待state变成NORMAL;
  4. 通过WaitNode类封装当前线程,并通过UNSAFE添加到waiters链表;
  5. 最终通过LockSupport的park或parkNanos挂起线程;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DonaldCen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值