【Java高并发核心编程(卷2)】第一章:多线程原理与实战 —— 进程和线程、线程的创建、操作、原理、线程池、ThreadLocal

文章要配合源码看

1 进程和线程

进程的查看可以通过任务管理器查看

什么是进程

简单来说,进程是程序的一次启动执行。
程序是存放在硬盘中的可执行文件,主要包括代码指令和数据。
一个进程是一个程序的一次启动和执行,是操作系统将程序装入内存,给程序分配必要的系统资源,并且开始运行程序的指令。

进程的基本原理

进程的大致结构:
进程的大致结构

程序段 一般也被称为代码段。代码段是进程的程序指令在内存中的位置,包含需要执行的指令集合;

数据段 是进程的操作数据在内存中的位置,包含需要操作的数据集合;

程序控制块(ProgramControl Block,PCB)包含进程的描述信息和控制信息,是进程存在的唯一标志。

PCB主要由四大部分组成:

进程的描述信息。主要包括:进程ID和进程名称,进程ID是唯一的,代表进程的身份;进程状态,比如运行、就绪、阻塞;进程优先级,是进程调度的重要依据。

进程的调度信息。主要包括:程序起始地址,程序的第一行指令的内存地址,从这里开始程序的执行;通信信息,进程间通信时的消息队列。

进程的资源信息。主要包括:内存信息,内存占用情况和内存管理所用的数据结构;I/O设备信息,所用的I/O设备编号及相应的数据结构;文件句柄,所打开文件的信息。

进程上下文。主要包括执行时各种CPU寄存器的值、当前程序计数器(PC)的值以及各种栈的值等,即进程的环境。在操作系统切换进程时,当前进程被迫让出CPU,当前进程的上下文就保存在PCB结构中,供下次恢复运行时使用。

Java编写的程序都运行在Java虚拟机(JVM)中,每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程。在这个JVM进程内部,所有Java程序代码都是以线程来运行的。JVM找到程序的入口点main()方法,然后运行main()方法,这样就产生了一个线程,这个线程被称为主线程。当main()方法结束后,主线程运行完成,JVM进程也随即退出。

线程的基本原理

线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。

线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。

线程是指“进程代码段”的一次顺序执行流程。线程是CPU调度的最小单位。一个进程可以有一个或多个线程,各个线程之间共享进程的内存空间、系统资源,进程仍然是操作系统资源分配的最小单位。
在这里插入图片描述
在线程的结构中,线程描述信息即线程的基本信息,主要包括:
(1)线程ID(Thread ID,线程标识符)。线程的唯一标识,同一个进程内不同线程的ID不会重叠。
(2)线程名称。主要是方便用户识别,用户可以指定线程的名字,如果没有指定,系统就会自动分配一个名称。
(3)线程优先级。表示线程调度的优先级,优先级越高,获得CPU的执行机会就越大。
(4)线程状态。表示当前线程的执行状态,为新建、就绪、运行、阻塞、结束等状态中的一种。
(5)其他。例如是否为守护线程等,后面会详细介绍。在线程的结构中,程序计数器很重要,它记录着线程下一条指令的代码段内存地址。

程序计数器记录着线程下一条指令的代码段内存地址。

进程与线程的区别

(1)线程是“进程代码段”的一次顺序执行流程。一个进程由一个或多个线程组成,一个进程至少有一个线程。
(2)线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高。
(3)线程是出于高并发的调度诉求从进程内部演进而来的。线程的出现既充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题。
(4)进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)。
(5)切换速度不同:线程上下文切换比进程上下文切换要快得多。所以,有的时候,线程也称为轻量级进程。

2 创建线程的四种方法

Thread类简介

Thread具有id、name、priority、deamon、threadStatus、启动和运行、获取线程 等属性和方法。

简述几个常用的

priority

public final void setPriority(intpriority),设置线程优先级。Java线程的最大优先级值为10,最小值为1,默认值为5。

deamon

守护线程,是在进程运行时提供某种后台服务的线程,比如垃圾回收(GC)线程。

threadStatus

public Thread.State getState(),返回表示当前线程的执行状态,Thread的内部静态枚举类State用于定义Java线程的所有状态,具体如下:NEW(新建),RUNNABLE(就绪、运行),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(计时等待),TERMINATED(结束)

Java线程状态中,就绪状态和运行状态在内部都用RUNNABLE。就绪状态表示线程具备运行条件,正在等待获取CPU时间片;运行状态表示线程已经获取了CPU时间片,CPU正在执行线程代码逻辑。

start 和 run

public void start()

用来启动一个线程,当调用start()方法后,JVM才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中会为相应的线程分配需要的资源。

public void run()

作为线程代码逻辑的入口方法。run()方法不是由用户程序来调用的,当调用start()方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run()方法体去执行具体的用户线程代码。

创建线程

创建一个空线程
public class EmptyThreadDemo
{
    public static void main(String args[]) throws InterruptedException
    {
        //使用Thread类创建和启动线程
        Thread thread = new Thread();
        Print.cfo("线程名称:" + thread.getName());
        Print.cfo("线程ID:" + thread.getId());
        Print.cfo("线程状态:" + thread.getState());
        Print.cfo("线程优先级:" + thread.getPriority());
        thread.start();
        Print.cfo("线程状态:" + thread.getState());
        ThreadUtil.sleepMilliSeconds(10);
    }
}

[EmptyThreadDemo.main]:线程名称:Thread-0
[EmptyThreadDemo.main]:线程ID:19
[EmptyThreadDemo.main]:线程状态:NEW
[EmptyThreadDemo.main]:线程优先级:5
[EmptyThreadDemo.main]:线程状态:RUNNABLE

thread调用start()方法,新线程会去调用run()方法,注意thread中的run()方法:

    public void run() {
        if (target != null) {
            target.run();
        }
    }

target是Thread类的一个实例属性,默认为空。在这个例子中,thread线程的target属性默认为null。所以在thread线程执行时,其run()方法其实什么也没有做,线程就执行完了。

继承Thread类

(1)需要继承Thread类,创建一个新的线程类。
(2)同时重写run()方法,将需要并发执行的业务代码编写在run()方法中。

public class CreateDemo {

    public static final int MAX_TURN = 5;
    static int threadNo = 1;

    static class DemoThread extends Thread {
        public DemoThread() {
            super("Mall-" + threadNo++);
        }

        public void run() {
            Print.cfo(getName() + ", 运行");
        }
    }

    public static void main(String args[]) throws InterruptedException {
        //方法一:使用Thread子类创建和启动线程
        new DemoThread().start();
        new DemoThread().start();
        Print.cfo(getCurThreadName() + " 运行结束.");
    }
}
实现Runnable接口

Thread类中的run方法

package java.lang;
public class Thread implements Runnable {
	...
	private Runnable target; // 执行目标
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    public Thread(Runnable target) {
    	// 包含执行目标的构造器
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
}
public class CreateDemo2 {
    public static final int MAX_TURN = 3;
    static int threadNo = 1;
    static class RunTarget implements Runnable  //① 实现Runnable接口
    {
        public void run()  //② 在这些写业务逻辑
        {
            for (int j = 1; j < MAX_TURN; j++) Print.cfo(getCurThreadName() + ", 轮次:" + j);
            Print.cfo(getCurThreadName() + " 运行结束.");
        }
    }
    public static void main(String args[]) throws InterruptedException {
        for (int i = 0; i < 2; i++) {
            Runnable target = new RunTarget();
            new Thread(target, "RunnableThread" + threadNo++).start();
        }
    }
}

[CreateDemo2$RunTarget.run]:RunnableThread1, 轮次:1
[CreateDemo2$RunTarget.run]:RunnableThread2, 轮次:1
[CreateDemo2$RunTarget.run]:RunnableThread1, 轮次:2
[CreateDemo2$RunTarget.run]:RunnableThread2, 轮次:2
[CreateDemo2$RunTarget.run]:RunnableThread2 运行结束.
[CreateDemo2$RunTarget.run]:RunnableThread1 运行结束.

值得注意的是,run()方法实现版本中在获取当前线程的名称时,所用的方法是在外部类ThreadUtil中定义的getCurThreadName()静态方法,而不是Thread类的getName()实例方法。原因是:这个RunTarget内部类和Thread类不再是继承关系,无法直接调用Thread类的任何实例方法。

通过实现Runnable接口的方式创建的执行目标类,如果需要访问线程的任何属性和方法,必须通过Thread.currentThread()获取当前的线程对象,通过当前线程对象间接访问。

通过继承Thread类的方式创建的线程类,可以在子类中直接调用Thread父类的方法访问当前线程的名称、状态等信息。这也是使用Runnable实现异步执行与继承Thread方法实现异步执行不同的地方。

实现Runnable接口创建线程目标类的优点

(1)可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。

(2)逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。

继承Thread类或者实现Runnable接口这两种方式来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执行的结果。

使用Callable和FutureTask创建线程

在这里插入图片描述

使用Callable创建线程
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型形参类型。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。
Callable实例能否和Runnable实例不一样,不能作为Thread线程实例的target来使用呢。Thread的target属性的类型为Runnable,而Callable接口与Runnable接口之间没有任何继承关系,并且二者唯一的方法在名字上也不同。RunnableFuture接口在Callable接口与Thread线程之间起到搭桥作用。

RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture继承了Runnable接口,从而保证了其实例可以作为Thread线程实例的target目标;同时,RunnableFuture通过继承Future接口,保证了可以获取未来的异步执行结果。

Future接口

Future接口至少提供了三大功能:
(1)能够取消异步执行中的任务。
(2)判断异步任务是否执行完成。
(3)获取异步任务完成后的执行结果。

public interface Future<V> {
	// 取消异步任务的执行。
    boolean cancel(boolean mayInterruptIfRunning);

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

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

	// 阻塞性获取异步任务执行的结果。
    V get() throws InterruptedException, ExecutionException;

	// 设置时限,(调用线程)阻塞性地获取异步任务执行的结果。
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask类

FutureTask类实现了RunnableFuture接口。RunnableFuture实现了Future接口和Runnable接口。FutureTask既能作为一个Runnable类型的target执行目标直接被Thread执行,又能作为Future异步任务来获取Callable的计算结果。

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

通过线程池创建
// 创建线程池
private static ExecutorService pool = Executors.newFixedThreadPool(3);
	//方法一:执行一个 Runnable类型的target执行目标实例,无返回
    void execute(Runnable command);
     
    //方法二:提交一个 Callable类型的target执行目标实例, 返回一个Future异步任务实例
    <T> Future<T> submit(Callable<T> task);  
                         
    //方法三:提交一个 Runnable类型的target执行目标实例, 返回一个Future异步任务实例
    Future<?> submit(Runnable task);

execute(…)与submit(…)方法的区别如下:

(1)接收的参数不一样

submit()可以接收两种入参:1、无返回值的Runnable类型的target执行目标实例,2、有返回值的Callable类型的target执行目标实例。

execute()仅仅接收无返回值的target执行目标实例,或者无返回值的Thread实例。

(2)submit()有返回值,而execute()没有

submit()方法在提交异步target执行目标之后会返回Future异步任务实例,以便对target的异步执行过程进行控制,比如取消执行、获取结果等。

execute()没有任何返回,target执行目标实例在执行之后没有办法对其异步执行过程进行控制,只能任其执行结束。

3 线程的核心原理

线程的生命周期

Java中线程的生命周期分为6种状态。Thread类有一个实例属性和一个实例方法专门用于保存和获取线程的状态。其中,用于保存线程Thread实例状态的实例属性为:

private int threadStatus;

hread类用于获取线程状态的实例方法为:

public Thread.State getState();

状态有六种,

(1)NEW(新建):创建成功,但是没有调用start()启动,线程实例都处于NEW状态

(2)RUNNABLE(就绪、运行):调用了start(),如果线程获取CPU时间片开始执行,JVM将异步调用线程的run()执行其业务代码。

(3)BLOCKED(阻塞):线程等待获取锁、IO阻塞,会引起线程阻塞

(4)WAITING(等待):线程处于不限时等待状态

(5)TIMED_WAITING(计时等待):线程处于限时等待状态

(6)TERMINATED(结束):线程在run()方法执行完成之后就变成TERMINATED状态。如果在run()执行过程中发生了RuntimeException而没有被捕获,run()将被异常终止,线程也会变成TERMINATED状态。

使用Jstack工具查看线程状态

CPU100%,可以使用jstack来查看,用于生成或导出(DUMP)JVM虚拟机运行实例当前时刻的线程快照。

在服务器上执行 jstack <pid> 即可

Jstack指令所输出的信息中包含以下重要信息:
(1)tid:线程实例在JVM进程中的id。
(2)nid:线程实例在操作系统中对应的底层线程的线程id。
(3)prio:线程实例在JVM进程中的优先级。
(4)os_prio:线程实例在操作系统中对应的底层线程的优先级。
(5)线程状态:如runnable、waiting oncondition等。

线程的interrupt操作

(1)如果此线程处于阻塞状态(如调用了Object.wait()方法),就会立马退出阻塞,并抛出InterruptedException异常,线程就可以通过捕获InterruptedException来做一定的处理,然后让线程退出。
(2)如果此线程正处于运行之中,线程就不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。

线程的join操作

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

//重载版本1:把当前线程变为TIMED_WAITING,直到被合并线程执行结束
public final void join() throws InterruptedException

//重载版本2:把当前线程变为TIMED_WAITING,直到被合并线程执行结束,或者等待被合并线程执行millis的时间
public final synchronized void join(long millis) throws InterruptedException

//重载版本3:把当前线程变为TIMED_WAITING,直到被合并线程执行结束,或者等待被合并线程执行millis+nanos的时间
public final synchroinzed void join(long millis, int nanos) throws InterruptedException

注:
(1)join()方法是实例方法,需要使用被合并线程的句柄(或者指针、变量)去调用,如threadb.join()
(2)如果设置了被合并线程的执行时间millis(或者millis+nanos),线程在millis时间后不一定变为RUNNABLE。
(3)如果主动方合并线程在等待时被中断,就会抛出InterruptedException。

join的操作:
在这里插入图片描述

线程的yield操作

yield的作用是让目前正在执行的线程放弃当前的执行,让出CPU的执行权限,使得CPU去执行其他的线程。

(1)yield仅能使一个线程从运行状态转到就绪状态,而不是阻塞状态。
(2)yield不能保证使得当前正在运行的线程迅速转换到就绪状态。
(3)即使完成了迅速切换,系统通过线程调度机制从所有就绪线程中挑选下一个执行线程时,就绪的线程有可能被选中,也有可能不被选中,其调度的过程受到其他因素(如优先级)的影响。

线程的daemon操作

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

相关方法:

// daemon状态
private boolean daemon = false;

// 设置daemon
public final void setDaemon(boolean on);

// 获取daemon状态
public final boolean isDaemon();

几个要点:

(1)守护线程必须在启动前将其守护状态设置为true,启动之后不能再将用户线程设置为守护线程,否则JVM会抛出一个InterruptedException异常

(2)守护线程存在被JVM强行终止的风险,所以在守护线程中尽量不去访问系统资源,如文件句柄、数据库连接等。守护线程被强行终止时,可能会引发系统资源操作不负责任的中断,从而导致资源不可逆的损坏。

(3)守护线程创建的线程也是守护线程。在守护线程中创建的线程,新的线程都是守护线程。在创建之后,如果通过调用setDaemon(false)将新的线程显式地设置为用户线程,新的线程可以调整成用户线程。

4 线程池

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

线程池主要解决了以下两个问题:

(1)提升性能:线程池能独立负责线程的创建、维护和分配。不需要自己创建线程,将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大限度地对已经创建的线程进行复用,使得性能提升明显。

(2)线程管理:每个线程池会保持一些基本的线程统计信息,例如完成的任务数量、空闲时间等,以便对线程进行有效管理,使得能对所接收到的异步任务进行高效调度。

JUC的线程池架构

在这里插入图片描述
1.Executor

Executor是Java异步目标任务的“执行者”接口,其目标是执行目标任务。“执行者”Executor提供了execute()接口来执行已提交的Runnable执行目标实例。Executor作为执行者的角色,其目的是提供一种将“任务提交者”与“任务执行者”分离开来的机制。它只包含一个函数式方法:

void execute(Runnable command)

2.ExecutorService

ExecutorService继承于Executor。它是Java异步目标任务的“执行者服务接”口,对外提供异步任务的接收服务。ExecutorService提供了“接收异步任务并转交给执行者”的方法,如submit系列方法、invoke系列方法等,具体如下:

//向线程池提交单个异步任务
<T> Future<T> submit(Callable<T> task);//向线程池提交批量异步任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

3.AbstractExecutorService

AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。AbstractExecutorService存在的目的是为ExecutorService中的接口提供默认实现。

4.ThreadPoolExecutor

ThreadPoolExecutor就是大名鼎鼎的“线程池”实现类,它继承于AbstractExecutorService抽象类。ThreadPoolExecutor是JUC线程池的核心实现类。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。

5.ScheduledExecutorService

ScheduledExecutorService是一个接口,它继承于ExecutorService。它是一个可以完成“延时”和“周期性”任务的调度线程池接口,其功能和Timer/TimerTask类似。

6.ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承于ThreadPoolExecutor,它提供了ScheduledExecutorService线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。ScheduledThreadPoolExecutor类似于Timer,但是在高并发程序中,ScheduledThreadPoolExecutor的性能要优于Timer。

7.Executors

Executors是一个静态工厂类,它通过静态工厂方法返回ExecutorService、ScheduledExecutorService等线程池示例对象,这些静态工厂方法可以理解为一些快捷的创建线程池的方法。

Executors的四种快捷创建线程池的方法

1.newSingleThreadExecutor

创建“单线程化线程池”,该方法用于创建一个“单线程化线程池”,也就是只有一个线程的线程池,所创建的线程池用唯一的工作线程来执行任务,使用此方法创建的线程池能保证所有任务按照指定顺序(如FIFO)执行。

2.newFixedThreadPool

创建“固定数量的线程池”,该方法用于创建一个“固定数量的线程池”,其唯一的参数用于设置池中线程的“固定数量”。

(1)如果线程数没有达到“固定数量”,每次提交一个任务线程池内就创建一个新线程,直到线程达到线程池固定的数量。

(2)线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

(3)在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,新任务会进入阻塞队列中(无界的阻塞队列)。

适用场景:

需要任务长期执行的场景。“固定数量的线程池”的线程数能够比较稳定地保证一个数,能够避免频繁回收线程和创建线程,故适用于处理CPU密集型的任务,在CPU被工作线程长时间占用的情况下,能确保尽可能少地分配线程。

弊端:

内部使用无界队列来存放排队任务,当大量任务超过线程池最大容量需要处理时,队列无限增大,使服务器资源迅速耗尽。

3.newCachedThreadPool

创建“可缓存线程池”,该方法用于创建一个“可缓存线程池”,如果线程池内的某些线程无事可干成为空闲线程,“可缓存线程池”可灵活回收这些空闲线程。

(1)在接收新的异步任务target执行目标实例时,如果池内所有线程繁忙,此线程池就会添加新线程来处理任务。

(2)此线程池不会对线程池大小进行限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

(3)如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,就会回收空闲(60秒不执行任务)线程。

适用场景:

需要快速处理突发性强、耗时较短的任务场景,如Netty的NIO处理场景、REST API接口的瞬时削峰场景。“可缓存线程池”的线程数量不固定,只要有空闲线程就会被回收;接收到的新异步任务执行目标,查看是否有线程处于空闲状态,如果没有就直接创建新的线程。

弊端:

线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能会因创建线程过多而导致资源耗尽。

4.newScheduledThreadPool

创建“可调度线程池”,该方法用于创建一个“可调度线程池”,即一个提供“延时”和“周期性”任务调度功能的ScheduledExecutorService类型的线程池。

适用场景:

周期性地执行任务的场景。Spring Boot中的任务调度器,底层借助了JUC的ScheduleExecutorService“可调度线程池”实现,并且可以通过@Configuration配置类型的Bean。

线程池的标准创建方式

ThreadPoolExecutor创建线程池
public ThreadPoolExecutor(
	int corePoolSize, // 核心线程数
	int maximumPoolSize, // 最大线程数
	long keepAliveTime, // 最大空闲时长
	TimeUnit unit, // 时间单位
	VlockintQueue<Runnable> workQueue, // 阻塞队列
	ThreadFactory threadFactory, // 新线程生成方式
	RejectedExecutionHandler handler // 拒绝策略
)

在这里插入图片描述

submit和execute提交任务的区别
// Execute 方式
void execute(Runnalbe command);

// submit 方式
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnalbe task);

(1)二者所接收的参数不一样

Execute()方法只能接收Runnable类型的参数。

submit()方法可以接收Callable、Runnable两种类型的参数。

Callable类型的任务是可以返回执行结果的,而Runnable类型的任务不可以返回执行结果。

Runnable和Callable的主要区别为:Callable允许有返回值,Runnable不允许有返回值;Runnable不允许抛出异常,Callable允许抛出异常。

(2)submit()提交任务后会有返回值,而execute()没有

(3)submit()方法返回的Future对象(异步执行实例),可以进行异步执行过程中的异常捕获。

ThreadFactory(线程工厂)
BlockingQueue

(1)ArrayBlockingQueue:是一个数组实现的有界阻塞队列(有界队列),队列中的元素按FIFO排序。ArrayBlockingQueue在创建时必须设置大小,接收的任务超出corePoolSize数量时,任务被缓存到该阻塞队列中,任务缓存的数量只能为创建时设置的大小,若该阻塞队列已满,则会为新的任务创建线程,直到线程池中的线程总数大于maximumPoolSize。

(2)LinkedBlockingQueue:是一个基于链表实现的阻塞队列,按FIFO排序任务,可以设置容量(有界队列),不设置容量则默认使用Integer.Max_VALUE作为容量(无界队列)。该队列的吞吐量高于ArrayBlockingQueue。如果不设置LinkedBlockingQueue的容量(无界队列),当接收的任务数量超出corePoolSize时,则新任务可以被无限制地缓存到该阻塞队列中,直到资源耗尽。有两个快捷创建线程池的工厂方法Executors.newSingleThreadExecutor和Executors.newFixedThreadPool使用了这个队列,并且都没有设置容量(无界队列)。

(3)PriorityBlockingQueue:是具有优先级的无界队列。

(4)DelayQueue:这是一个无界阻塞延迟队列,底层基于PriorityBlockingQueue实现,队列中每个元素都有过期时间,当从队列获取元素(元素出队)时,只有已经过期的元素才会出队,队列头部的元素是过期最快的元素。快捷工厂方法Executors.newScheduledThreadPool所创建的线程池使用此队列。

(5)SynchronousQueue:(同步队列)是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程的调用移除操作,否则插入操作一直处于阻塞状态,其吞吐量通常高于LinkedBlockingQueue。快捷工厂方法Executors.newCachedThreadPool所创建的线程池使用此队列。与前面的队列相比,这个队列比较特殊,它不会保存提交的任务,而是直接新建一个线程来执行新来的任务。

调度器的钩子方法

ThreadPoolExecutor线程池调度器为每个任务执行前后都提供了钩子方法。ThreadPoolExecutor类提供了三个钩子方法(空方法),这三个钩子方法一般用作被子类重写,具体如下:

// 任务执行之前的狗子方法(前钩子)
protected void beforeExecute(Thread t, Runnable r) { }
// 任务执行之后的钩子方法(后钩子)
protected void afterExecute(Thread t, Runnable r) { }
// 线程池终止时的钩子方法(停止钩子)
protected void terminated(Thread t, Runnable r) { }

(1)beforeExecute:异步任务执行之前的钩子方法线程池工作线程在异步执行目标实例(如Runnable实例)前调用此钩子方法。此方法仍然由执行任务的工作线程调用。默认实现不执行任何操作,但可以在子类中对其进行自定义。此方法由执行目标实例的工作线程调用,可用于重新初始化ThreadLocal线程本地变量实例、更新日志记录、开始计时统计、更新上下文变量等。

(2)afterExecute:异步任务执行之后的钩子方法线程池工作线程在异步执行目标实例后调用此钩子方法。此方法仍然由执行任务的工作线程调用。此钩子方法的默认实现不执行任何操作,可以在调度器子类中对其进行自定义。此方法由执行目标实例的工作线程调用,可用于清除ThreadLocal线程本地变量、更新日志记录、收集统计信息、更新上下文变量等。

(3)terminated:线程池终止时的钩子方法terminated钩子方法在Executor终止时调用,默认实现不执行任何操作。

RejectedExecutionHandler拒绝策略

任务被拒绝有两种情况:
(1)线程池已经被关闭。
(2)工作队列已满且maximumPoolSize已满。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Geek-Banana

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

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

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

打赏作者

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

抵扣说明:

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

余额充值