线程的创建

Java进程中每一个线程都对应着一个Thread实例。线程的描述信息在Thread的实例属性中得到保存,供JVM进行线程管理和调度时使用。

Thread类除定义了很多操作线程实例的成员方法之外,还有一系列类的静态方法。比如Thread.currentThread()静态方法就是其中之一,该方法的作用是取得当前CPU内核上正在运行的线程实例。

说明虽然一个进程有很多个线程,但是在一个CPU内核上,同一时刻只能有一个线程是正在执行的,该线程也被叫作当前线程。

Thread类是Java多线程编程的基础。Java中创建线程虽然有三种方式,但是三种方式都会涉及Thread类。

1. Thread类详解

一个线程在Java中使用一个Thread实例来描述。Thread类是Java语言的一个重要的基础类,位于java.lang包中。Thread类有不少非常重要的属性和方法,用于存储和操作线程的描述信息,该类的结构图大致如图所示。

image-20220411155039691

接下来,为大家逐一介绍Thread类中比较重要的属性和方法。

  1. 线程ID

    1
    2
    private long tid; 
    //此属性用于保存线程的ID。这是一个private类型的属性,外部只能使用getId()方法访问线程的ID。
    1
    public long getId()                       // 获取线程ID,线程ID由JVM进行管理,在进程内唯一。
  2. 线程名称

    1
    private String name;                     // 该属性保存一个Thread线程实例的名字。
    1
    2
    3
    public final String getName() 					// 获取线程名称
    public final void setName(String name) //设置线程名称
    Thread(String threadName) //通过此构造方法给线程设置一个定制化的名字。
  3. 线程优先级

    1
    private int priority; // 保存一个Thread线程实例的优先级。
    1
    2
    public final int getPriority() //获取线程优先级。
    public final void setPriority(int priority) //设置线程优先级。Java线程的最大优先级值为10,最小值为1,默认值为5。这三个优先级值为三个常量值,在Thread类中使用类常量定义,

    三个类常量如下:

image-20220411155532045

  1. 是否为守护线程
1
2
private boolean daemon=false; 
//该属性保存Thread线程实例的守护状态,默认为false,表示是普通的用户线程,而不是守护线程。
1
2
public final void setDaemon(boolean on);
//将线程实例标记为守护线程或用户线程,如果参数值为true,那么将线程实例标记为守护线程。

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

  1. 线程的状态
1
private int threadStatus; // 该属性以整数的形式保存线程的状态。
1
2
public Thread.State getState() 
//返回表示当前线程的执行状态,为新建、就绪、运行、阻塞、结束等状态中的一种。

Thread的内部静态枚举类State用于定义Java线程的所有状态,具体如下:

image-20220411155848448

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

  1. 线程的启动和运行

    1
    2
    public void start(); //用来启动一个线程,当调用start()方法后,JVM才会开启一个新的线程来执行用户定义的线程代码逻辑,在这个过程中会为相应的线程分配需要的资源。
    public void run(); //作为线程代码逻辑的入口方法。run()方法不是由用户程序来调用的,当调用start()方法启动一个线程之后,只要线程获得了CPU执行时间,便进入run()方法体去执行具体的用户线程代码。

    总之,这两个方法非常重要,start()方法用于线程的启动,run()方法作为用户代码逻辑的执行入口。

  2. 取得当前线程

    1
    public static Thread currentThread(); //该方法是一个非常重要的静态方法,用于获取当前线程的Thread实例对象。什么是当前线程呢?就是当前在CPU上执行的线程。在没有其他的途径获取当前线程的实例对象的时候,可以通过Thread.currentThread()静态方法获取。

2. 创建

2.1 继承Thread类创建线程类

新线程如果需要并发执行自己的代码,需要做以下两件事情:

  1. 需要继承Thread类,创建一个新的线程类。
  2. 同时重写run()方法,将需要并发执行的业务代码编写在run()方法中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.crazymakercircle.mutithread.basic.create;
import com.crazymakercircle.util.Print;
import static com.crazymakercircle.util.ThreadUtil.getCurThreadName;

/**
* Created by 尼恩@疯狂创客圈.
*/

public class CreateDemo {

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

static class DemoThread extends Thread {
// 静态内部类是为了方便访问外部类的成员属性和方法,和线程的使用没有任何关系
public DemoThread() {
super("Mall-" + threadNo++);
//通过super()调用基类的Thread(StringthreadName)构造方法设置了线程的名称
}
public void run() {
for (int i = 1; i < MAX_TURN; i++) {
Print.cfo(getName() + ", 轮次:" + i);
}
Print.cfo(getName() + " 运行结束.");
}
}

public static void main(String args[]) throws InterruptedException {
Thread thread = null;
//方法一:使用Thread子类创建和启动线程
for (int i = 0; i < 2; i++) {
thread = new DemoThread();
thread.start();
}
Print.cfo(getCurThreadName() + " 运行结束.");
}
}

2.2 实现Runnable接口创建线程目标类

重温一下Thread类的run()方法的代码,其代码如下:

image-20220411162447823

在Thread类的run()方法中,如果target(执行目标)不为空,就执行target属性的run()方法。而target属性是Thread类的一个实例属性,并且target属性的类型为Runnable。

Thread类的target属性在什么情况下非空呢?Thread类有一系列的构造器,其中有多个构造器可以为target属性赋值,这些构造器包括如下两个:

1
2
3
1. public Thread(Runnable target)
2. public Thread(Runnable target,String name)
//使用这两个构造器传入target执行目标实例(Runnable实例),就可以直接通过Thread类的run()方法以默认方式实现,达到线程并发执行的目的。在这种场景下,可以不通过继承Thread类实现线程类的创建。

1. Runnable接口

Runnable是一个极为简单的接口,位于java.lang包中。接口中只有一个方法run(),具体的源代码如下:

image-20220411162613365

Runnable有且仅有一个抽象方法——void run(),代表被执行的用户业务逻辑的抽象,在使用的时候,将用户业务逻辑编写在Runnable实现类的run()的实现版本中。当Runnable实例传入Thread实例的target属性后,Runnable接口的run()的实现版本将被异步调用。

2. 通过实现Runnable接口创建线程类

创建线程的第二种方法就是实现Runnable接口,将需要异步执行的业务逻辑代码放在Runnable实现类的run()方法中,将Runnable实例作为target执行目标传入Thread实例。该方法的具体步骤如下:

  1. 定义一个新类实现Runnable接口。
  2. 实现Runnable接口中的run()抽象方法,将线程代码逻辑存放在该run()实现版本中。
  3. 通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器将该Runnable实例赋值给自己的target执行目标属性。
  4. 调用Thread实例的start()方法启动线程。
  5. 线程启动之后,线程的run()方法将被JVM执行,该run()方法将调用target属性的run()方法,从而完成Runnable实现类中业务代码逻辑的并发执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.crazymakercircle.mutithread.basic.create;
import com.crazymakercircle.util.Print;
import static com.crazymakercircle.util.ThreadUtil.getCurThreadName;

/**
* Created by 尼恩@疯狂创客圈.
*/

public class CreateDemo2 {
public static final int MAX_TURN = 5;
static int threadNo = 1;

static class RunTarget implements Runnable //① 实现Runnable接口
{
public void run() //② 在这些写业务逻辑
{
//说明值得注意的是,run()方法实现版本中在获取当前线程的名称时,所用的方法是在外部类ThreadUtil中定义的getCurThreadName()静态方法,而不是Thread类的getName()实例方法。原因是:这个RunTarget内部类和Thread类不再是继承关系,无法直接调用Thread类的任何实例方法。
// getCurThreadName()=Thread.currentThread().getname();
for (int j = 1; j < MAX_TURN; j++) {
Print.cfo(getCurThreadName() + ", 轮次:" + j);
}

Print.cfo(getCurThreadName() + " 运行结束.");
}
}

public static void main(String args[]) throws InterruptedException {
Thread thread = null;

//方法2.1:使用实现Runnable的实现类创建和启动线程

for (int i = 0; i < 2; i++) {
Runnable target = new RunTarget();
thread = new Thread(target, "RunnableThread" + threadNo++);
thread.start();
}

//方法2.2:使用实现Runnable的匿名类创建和启动线程

for (int i = 0; i < 2; i++) {
thread = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 1; j < MAX_TURN; j++) {
Print.cfo(getCurThreadName() + ", 轮次:" + j);
}
Print.cfo(getCurThreadName() + " 运行结束.");
}
}, "RunnableThread" + threadNo++);
thread.start();
}
//方法2.3:使用实现lambor表达式创建和启动线程
//在Runnable接口上声明了一个@FunctionalInterface注解。该注解的作用是:标记Runnable接口是一个“函数式接口”。在Java中,“函数式接口”是有且仅有一个抽象方法的接口。
//@FunctionalInterface注解不是必需的,只要一个接口符合“函数式接口”的定义,使用时加不加@FunctionalInterface注解都没有影响,都可以当作“函数式接口”来使用。
for (int i = 0; i < 2; i++) {
thread = new Thread(() ->
{
for (int j = 1; j < MAX_TURN; j++) {
Print.cfo(getCurThreadName() + ", 轮次:" + j);
}
Print.cfo(getCurThreadName() + " 运行结束.");
}, "RunnableThread" + threadNo++);
thread.start();
}
Print.cfo(getCurThreadName() + " 运行结束.");
}
}

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

image-20220411163105351

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

完成了Runnable的实现类后,需要调用Thread类的构造器创建线程,并将Runnable实现类的实例作为实参传入。可以调用的构造器(即构造函数)包括如下三个:

1
2
3
public Thread(Runnable target);
public Thread(Runnable target,String name);
public Thread(ThreadGroup group,Runnable target);

若调用以上第二个构造器构造线程时可以指定线程的名称,则实例如下:

image-20220411163117270

线程对象创建完成后,调用Thread线程实例的start()方法启动新线程的并发执行。这时,Runnable实例的run()方法会在新线程Thread的实例方法run()中被调用。

通过实现Runnable接口的方式创建线程目标类的优缺点通过实现Runnable接口的方式创建线程目标类有以下缺点:

  1. 所创建的类并不是线程类,而是线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能创建真正的线程。
  2. 如果访问当前线程的属性(甚至控制当前线程),不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前线程实例,才能访问和控制当前线程。

通过实现Runnable接口的方式创建线程目标类有以下优点:

  1. 可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。比如,当一个Dog类继承了Pet类,再要继承Thread类就不行了。所以在已经存在继承关系的情况下,只能使用实现Runnable接口的方式。
  2. 逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。
  3. 异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交,所以更多的做法是,通过实现Runnable接口创建异步执行任务,而不是继承Thread去创建异步执行任务。

2.3 使用Callable和FutureTask创建线程

前面已经介绍了继承Thread类或者实现Runnable接口这两种方式来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执行的结果。这是一个比较大的问题,很多场景都需要获取异步执行的结果,通过Runnable无法实现,是因为它的run()方法不支持返回值。为了解决异步执行的结果问题,Java语言在1.5版本之后提供了一种新的多线程创建方法:通过Callable接口和FutureTask类相结合创建线程。

1. Callable接口

Callable接口位于java.util.concurrent包中,源代码如下:

image-20220411223501758

Callable接口是一个泛型接口,也是一个“函数式接口”。其唯一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型形参类型。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。

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

问题:Callable实例能否和Runnable实例一样,作为Thread线程实例的target来使用呢?答案是不行。Thread的target属性的类型为Runnable,而Callable接口与Runnable接口之间没有任何继承关系,并且二者唯一的方法在名字上也不同。显而易见,Callable接口实例没有办法作为Thread线程实例的target来使用。既然如此,那么该如何使用Callable接口创建线程呢?一个在Callable接口与Thread线程之间起到搭桥作用的重要接口马上就要登场了。

2. RunnableFuture接口

这个重要的中间搭桥接口就是RunnableFuture接口,该接口与Runnable接口、Thread类紧密相关。

与Callable接口一样,RunnableFuture接口也位于java.util.concurrent包中,使用的时候需要用import导入。

RunnableFuture是如何在Callable与Thread之间实现搭桥功能的呢?RunnableFuture接口实现了两个目标:一是可以作为Thread线程实例的target实例,二是可以获取异步执行的结果。它是如何做到一箭双雕的呢?请看RunnableFuture接口的代码:

image-20220411223629562

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

3. Future接口

Future接口至少提供了三大功能:

  1. 能够取消异步执行中的任务。
  2. 判断异步任务是否执行完成。
  3. 获取异步任务完成后的执行结果。Future接口的源代码如下:

image-20220411223721124

对Future接口的主要方法详细说明如下:

  • V get():获取异步任务执行的结果。注意,这个方法的调用是阻塞性的。如果异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到异步任务执行完成,其异步结果返回给调用线程。

  • V get(Long timeout,TimeUnit unit):设置时限,(调用线程)阻塞性地获取异步任务执行的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一个阻塞时长限制,不会无限制地阻塞和等待,如果其阻塞时间超过设定的timeout时间,该方法将抛出异常,调用线程可捕获此异常。

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

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

  • boolean cancel(boolean mayInterruptRunning):取消异步任务的执行。

4. FutureTask类

FutureTask类是Future接口的实现类,提供了对异步任务的操作的具体实现。但是,FutureTask类不仅实现了Future接口,还实现了Runnable接口,或者更加准确地说,FutureTask类实现了RunnableFuture接口。

前面讲到RunnableFuture接口很关键,既可以作为Thread线程实例的target目标,又可以获取并发任务执行的结果,是Thread与Callable之间一个非常重要的搭桥角色。但是,RunnableFuture只是一个接口,无法直接创建对象,如果需要创建对象,就需用到它的实现类——FutureTask。所以说,FutureTask类才是真正的在Thread与Callable之间搭桥的类。

FutureTask类的UML关系图大致如图所示。

image-20220411224008867

从FutureTask类的UML关系图可以看到:FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口,所以FutureTask既能作为一个Runnable类型的target执行目标直接被Thread执行,又能作为Future异步任务来获取Callable的计算结果。

FutureTask如何完成多线程的并发执行、任务结果的异步获取呢?FutureTask内部有一个Callable类型的成员——callable实例属性,具体如下:

image-20220411224102671

callable实例属性用来保存并发执行的Callable类型的任务,并且callable实例属性需要在FutureTask实例构造时进行初始化。FutureTask类实现了Runnable接口,在其run()方法的实现版本中会执行callable成员的call()方法。此外,FutureTask内部还有另一个非常重要的Object类型的成员——outcome实例属性:

image-20220411224116293

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

5. 使用Callable和FutureTask创建线程的具体步骤

通过FutureTask类和Callable接口的联合使用可以创建能够获取异步执行结果的线程,具体步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.crazymakercircle.mutithread.basic.create;
import com.crazymakercircle.util.Print;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import static com.crazymakercircle.util.ThreadUtil.getCurThreadName;

/**
* Created by 尼恩@疯狂创客圈.
*/

public class CreateDemo3 {

public static final int MAX_TURN = 5;
public static final int COMPUTE_TIMES = 100000000;

// 1.创建一个Callable接口的实现类,并实现其call()方法,编写好异步执行的具体逻辑,可以有返回值。
static class ReturnableTask implements Callable<Long> {
//返回并发执行的时间
public Long call() throws Exception {
// 这里call()方法中业务逻辑的返回结果是call()方法从进入到出来的执行时长
long startTime = System.currentTimeMillis();
Print.cfo(getCurThreadName() + " 线程运行开始.");
Thread.sleep(1000);

for (int i = 0; i < COMPUTE_TIMES; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis() - startTime;
Print.cfo(getCurThreadName() + " 线程运行结束.");
return used;
}
}

public static void main(String args[]) throws InterruptedException {
ReturnableTask task = new ReturnableTask();
//2. 使用Callable实现类的实例构造一个FutureTask实例。
FutureTask<Long> futureTask = new FutureTask<Long>(task);
// 3. 使用FutureTask实例作为Thread构造器的target入参,构造新的Thread线程实例。
Thread thread = new Thread(futureTask, "returnableThread");
thread.start();
// 4. 调用Thread实例的start()方法启动新线程,启动新线程的run()方法并发执行。其内部的执行过程为:启动Thread实例的run()方法并发执行后,会执行FutureTask实例的run()方法,最终会并发执行Callable实现类的call()方法。
Thread.sleep(500);
Print.cfo(getCurThreadName() + " 让子弹飞一会儿.");
Print.cfo(getCurThreadName() + " 做一点自己的事情.");
for (int i = 0; i < COMPUTE_TIMES / 2; i++) {
int j = i * 10000;
}

Print.cfo(getCurThreadName() + " 获取并发任务的执行结果.");

try {
// 5. 调用FutureTask对象的get()方法阻塞性地获得并发线程的执行结果。
Print.cfo(thread.getName() + "线程占用时间:" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Print.cfo(getCurThreadName() + " 运行结束.");
}
}

main线程和returnableThread线程的执行流程大致如图所示:

image-20220411230256105

FutureTask的Callable成员的call()方法执行完成后,会将结果保存在FutureTask内部的outcome实例属性中。

这里有两种情况:

  1. futureTask的结果outcome不为空,callable.call()执行完成。在这种情况下,futureTast.get会直接取回outcome结果,返回给main线程(结果获取线程)。
  2. futureTask的结果outcome为空,callable.call()还没有执行完。在这种情况下,main线程作为结果获取线程会被阻塞住,一直阻塞到callable.call()执行完成。当执行完后,最终结果会保存到outcome中,futureTask会唤醒main线程,去提取callable.call()执行结果。

2.4 通过线程池创建线程

前面的示例中,所创建的Thread实例在执行完成之后都销毁了,这些线程实例都是不可复用的。实际上创建一个线程实例在时间成本、资源耗费上都很高(稍后会介绍),在高并发的场景中,断然不能频繁进行线程实例的创建与销毁,而是需要对已经创建好的线程实例进行复用,这就涉及线程池的技术。Java中提供了一个静态工厂来创建不同的线程池,该静态工厂为Executors工厂类。

1.线程池的创建与执行目标提交

通过Executors工厂类创建一个线程池,一个简单的示例如下:

image-20220411230629720

ExecutorService是Java提供的一个线程池接口,每次我们在异步执行target目标任务的时候,可以通过ExecutorService线程池实例去提交或者执行。ExecutorService实例负责对池中的线程进行管理和调度,并且可以有效控制最大并发线程数,提高系统资源的使用率,同时提供定时执行、定频执行、单线程、并发数控制等功能。

向ExecutorService线程池提交异步执行target目标任务的常用方法有:

image-20220411230707943

2.线程池的使用实战使用

Executors创建线程池,然后使用ExecutorService线程池执行或者提交target执行目标实例的示例代码,大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.crazymakercircle.mutithread.basic.create;
import com.crazymakercircle.util.Print;
import java.util.concurrent.*;
import static com.crazymakercircle.util.ThreadUtil.*;

/**
* Created by 尼恩@疯狂创客圈.
*/

public class CreateDemo4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个包含三个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new DemoThread()); //执行线程实例
//执行Runnable执行目标实例
pool.execute(new Runnable() {
@Override
public void run() {
for (int j = 1; j < MAX_TURN; j++) {
Print.cfo(getCurThreadName() + ", 轮次:" + j);
sleepMilliSeconds(10);
}
}
});
//提交Callable 执行目标实例
Future future = pool.submit(new ReturnableTask());
Long result = (Long) future.get();
Print.cfo("异步任务的执行结果为:" + result);

sleepSeconds(Integer.MAX_VALUE);

}

static class DemoThread implements Runnable {
@Override
public void run() {
for (int j = 1; j < MAX_TURN; j++) {
Print.cfo(getCurThreadName() + ", 轮次:" + j);
sleepMilliSeconds(10);
}
}
}

public static final int MAX_TURN = 5;
public static final int COMPUTE_TIMES = 100000000;

static class ReturnableTask implements Callable<Long> {
//返回并发执行的时间
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
Print.cfo(getCurThreadName() + " 线程运行开始.");
for (int j = 1; j < MAX_TURN; j++) {
Print.cfo(getCurThreadName() + ", 轮次:" + j);
sleepMilliSeconds(10);
}
long used = System.currentTimeMillis() - startTime;
Print.cfo(getCurThreadName() + " 线程运行结束.");
return used;
}
}
}

大家可以对比和分析一下这些线程池中线程的名称和普通线程的名称有何不同。

ExecutorService线程池的execute(…)与submit(…)方法的区别如下。

  1. 接收的参数不一样submit()可以接收两种入参:无返回值的Runnable类型的target执行目标实例和有返回值的Callable类型的target执行目标实例。而execute()仅仅接收无返回值的target执行目标实例,或者无返回值的Thread实例
  2. submit()有返回值,而execute()没有submit()方法在提交异步target执行目标之后会返回Future异步任务实例,以便对target的异步执行过程进行控制,比如取消执行、获取结果等。execute()没有任何返回,target执行目标实例在执行之后没有办法对其异步执行过程进行控制,只能任其执行,直到其执行结束。