[TOC]

JUC之创建线程的几种方式

继承Thread类创建线程

​ 通过继承Thread类创建一个线程类,新的 线程子类重写了Thread的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
public class javaJUCstudy {
public static final int MAX_TURN = 5;
public static String getCurThreadName(){
return Thread.currentThread().getName();
}
static int threadId = 1;
public static void main(String[] args) {
Thread h;
for (int i = 0; i < 2; i++) {
h = new TestThread();
h.start();
}
System.out.println(getCurThreadName()+"运行结束");
}
}
class TestThread extends Thread{
public TestThread(){
super("TestThread:"+javaJUCstudy.threadId++);
}
public void run(){
for (int i = 0; i < javaJUCstudy.MAX_TURN; i++) {
System.out.println(getName()+",轮次:"+i);
}
System.out.println(getName()+"结束");
}
}

​ 执行结果:

main运行结束
TestThread:2,轮次:0
TestThread:1,轮次:0
TestThread:2,轮次:1
TestThread:1,轮次:1
TestThread:2,轮次:2
TestThread:1,轮次:2
TestThread:2,轮次:3
TestThread:1,轮次:3
TestThread:1,轮次:4
TestThread:2,轮次:4
TestThread:2结束
TestThread:1结束

​ TestThread类的关键点是重写了Thread类的run()方法, 将需要并发执行的用户业务代码编写在继承的run()方法中。

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

​ 通过继承Thread类并重写它的run()方法只是创建Java线程的一种方式。接下来要介绍的一种方式是通过实现现Runnable接口创建线程目标类,在这之前我们先来看看Thread的run方法。

1
2
3
4
5
6
7
8
9
10
11
12
public class Thread implements Runnable {
......
private Runnable target;//执行目标
public void run() {
if (target != null) {
target.run();//调用执行目标的run方法
}
}
public Thread(Runnable target) {//包含执行目标的构造器
init(null, target, "Thread-" + nextThreadNum(), 0);
}
}

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

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

    1. public Thread(Runnable target)
    1. public Thread(Runnable target,String name)

​ 使用这两个构造器传入target执行目标实例(Runnable实例), 就可以直接通过Thread类的run()方法以默认方式实现。而我们可以通过创建一个类来实现Runnbale接口来创建Runnable实例。通过实现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
package suan.JUC;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;

public class RunnableTest {
public static final int MAX_TURN = 5;
public static String getCurThreadName(){
return Thread.currentThread().getName();
}
static int threadId = 1;

public static void main(String[] args) throws InterruptedException {
Thread h;
for (int i = 0; i < 2; i++) {
Runnable r = new TestRunnable();
h = new Thread(r,"TestThread:"+threadId++);
h.start();
}
Thread.sleep(1000);
System.out.println(getCurThreadName()+"结束");
}
}
class TestRunnable implements Runnable{

@Override
public void run() {
for (int i = 0; i < RunnableTest.MAX_TURN; i++) {
System.out.println(Thread.currentThread().getName()+",轮次:"+i);
}
System.out.println(Thread.currentThread().getName()+"结束");
}
}

执行结果:

TestThread:1,轮次:0
TestThread:2,轮次:0
TestThread:1,轮次:1
TestThread:2,轮次:1
TestThread:1,轮次:2
TestThread:2,轮次:2
TestThread:1,轮次:3
TestThread:2,轮次:3
TestThread:2,轮次:4
TestThread:1,轮次:4
TestThread:2结束
TestThread:1结束
main结束

​ 实例中静态内部类RunTarget执行目标类,不再是继承Thread线程 类,而是实现Runnable接口,需要异步并发执行的代码逻辑被编写在 它的run()方法中。通过实现Runnable接口的方式创建的执行目标类,如果需要访问 线程的任何属性和方法,必须通过Thread.currentThread()获取当前 的线程对象,通过当前线程对象间接访问。

使用实现Runnable接口创建线程目标类的简便写法

​ 因为Runnable是一个函数式接口,在接口实现时可以使用Lambda 表达式提供匿名实现,来简化我们的代码。改进后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RunnableTest {
public static final int MAX_TURN = 5;
public static String getCurThreadName(){
return Thread.currentThread().getName();
}
static int threadId = 1;

public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 2; i++) {
new Thread(()->{
for (int j = 0; j < RunnableTest.MAX_TURN; j++) {
System.out.println(Thread.currentThread().getName()+",轮次:"+j);
}
System.out.println(Thread.currentThread().getName()+"结束");
},"name"+threadId++).start();
}
Thread.sleep(1000);
System.out.println(getCurThreadName()+"结束");
}
}

执行结果如下:

name1,轮次:0
name2,轮次:0
name2,轮次:1
name2,轮次:2
name2,轮次:3
name2,轮次:4
name2结束
name1,轮次:1
name1,轮次:2
name1,轮次:3
name1,轮次:4
name1结束
main结束

使用Callable和FutureTask 创建线程

​ 前面已经介绍了继承Thread类或者实现Runnable接口这两种方式 来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执 行的结果。为了解决异步执行的结果问题,Java语言在1.5版本之后提供了一 种新的多线程创建方法:通过Callable接口和FutureTask类相结合创 建线程。

Callable接口

Callable接口位于java.util.concurrent包中,查看它的Java源 代码,如下:

1
2
3
4
5
package java.util.concurrent;
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}

Callable接口是一个泛型接口,也是一个“函数式接口”。其唯 一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型 形参类型。call()抽象方法还有一个Exception的异常声明,容许方法 的实现版本的内部异常直接抛出,并且可以不予捕获。但Callable接口不能像Runnable接口那样作为Thread线程实例的taiget使用,这就要引入RunnableFuture接口了。

RunnableFuture接口

RunnableFuture接口与 Runnable接口、Thread类紧密相关,RunnableFuture接口实现了两个目标:一是可以作为Thread线程 实例的target实例,二是可以获取异步执行的结果。

1
2
3
4
package java.util.concurrent;
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}

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

Future接口

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

(1)能够取消异步执行中的任务。

(2)判断异步任务是否执行完成。

(3)获取异步任务完成后的执行结果。

Future接口的代码如下:

1
2
3
4
5
6
7
8
9
10
11
package java.util.concurrent;
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);//取消异步任务的执行。
boolean isCancelled();//获取异步任务的取消状态。如果任务完成前被取消,就返回true。
boolean isDone();//获取异步任务的执行状态。如果任务执行结束,就返回true。
V get() throws InterruptedException, ExecutionException;
//获取异步任务执行的结果。注意,这个方法的调用是阻塞性的。如果异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到异步任务执行完成,其异步结果返回给调用线程。
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
//设置时限,(调用线程)阻塞性地获取异步任务执行的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一个阻塞时长限制,不会无限制地阻塞和等待,如果其阻塞时间超过设定的timeout时间,该方法将抛出异常,调用线程可捕获此异常。
}
FutureTask类

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

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

机构图如下:

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

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

(1)创建一个Callable接口的实现类,并实现其call()方法,编 写好异步执行的具体逻辑,可以有返回值。

(2)使用Callable实现类的实例构造一个FutureTask实例。

(3)使用FutureTask实例作为Thread构造器的target入参,构造 新的Thread线程实例。

(4)调用Thread实例的start()方法启动新线程,启动新线程的 run()方法并发执行。其内部的执行过程为:启动Thread实例的run() 方法并发执行后,会执行FutureTask实例的run()方法,最终会并发执 行Callable实现类的call()方法。

(5)调用FutureTask对象的get()方法阻塞性地获得并发线程的 执行结果。

代码如下:

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
package suan.JUC;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureTaskTest {
public static final int MAX_TURN = 5;
public static final int COMPUTE_TIMES = 100000000;
public static String getCurThreadName(){
return Thread.currentThread().getName();
}

public static void main(String[] args) throws InterruptedException {
ReturnTask task = new ReturnTask();
FutureTask<Long> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask,"有返回值的异步线程");
thread.start();
Thread.sleep(1000);
System.out.println(getCurThreadName()+"等一会");
for (int i = 0; i < COMPUTE_TIMES/2; i++) {
int j = i * 10000;
}
System.out.println(getCurThreadName()+"获取异步执行的结果");
try {
System.out.println(thread.getName()+"线程占用时间"+futureTask.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("运行结束");
}
}
class ReturnTask implements Callable<Long> {

@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"线程开始运行");
Thread.sleep(1000);
for (int i = 0; i < FutureTaskTest.COMPUTE_TIMES; i++) {
int j = i * 10000;
}
long useTime = System.currentTimeMillis() - startTime;
System.out.println(Thread.currentThread().getName()+"线程运行结束");
return useTime;
}
}

运行结果如下:

有返回值的异步线程线程开始运行
main等一会
有返回值的异步线程线程运行结束
main获取异步执行的结果
有返回值的异步线程线程占用时间1010
运行结束

通过线程池创建线程

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

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

  1. 执行一个 Runnable类型的target执行目标实例,无返回 。

​ void execute(Runnable command);

  1. 提交一个 Callable类型的target执行目标实例, 返回一个 Future异步任务实例

​ Future submit(Callable task);

  1. 提交一个 Runnable类型的target执行目标实例, 返回一个 Future异步任务实例

​ Future submit(Runnable task);

代码实例:

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 suan.JUC;

import java.util.concurrent.*;

public class ExecutorsTest {
public static final int MAX_TURN = 5;
public static final int COMPUTE_TIMES = 100000000;
private static ExecutorService pool = Executors.newFixedThreadPool(3);

public static void main(String[] args) throws ExecutionException, InterruptedException {
pool.execute(new demoThread());
pool.execute(()->{
for (int i = 0; i < MAX_TURN; i++) {
System.out.println(Thread.currentThread().getName()+", 轮次: "+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Future<Long> submit = pool.submit(new ReturnTaskTest());
Long result = (Long)submit.get();
System.out.println("异步执行的结果:"+result);
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+"结束");
}
}
class demoThread implements Runnable{

@Override
public void run() {
for (int i = 0; i < ExecutorsTest.MAX_TURN; i++) {
System.out.println(Thread.currentThread().getName()+", 轮次: "+ i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ReturnTaskTest implements Callable<Long> {

@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+"线程开始运行");
Thread.sleep(1000);
for (int i = 0; i < ExecutorsTest.MAX_TURN; i++) {
System.out.println(Thread.currentThread().getName()+", 轮次: "+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long useTime = System.currentTimeMillis() - startTime;
System.out.println(Thread.currentThread().getName()+"线程运行结束");
return useTime;
}
}

执行结果:

pool-1-thread-1, 轮次: 0
pool-1-thread-1, 轮次: 1
pool-1-thread-1, 轮次: 2
pool-1-thread-2, 轮次: 0
pool-1-thread-3线程开始运行
pool-1-thread-1, 轮次: 3
pool-1-thread-2, 轮次: 1
pool-1-thread-1, 轮次: 4
pool-1-thread-2, 轮次: 2
pool-1-thread-2, 轮次: 3
pool-1-thread-2, 轮次: 4
pool-1-thread-3, 轮次: 0
pool-1-thread-3, 轮次: 1
pool-1-thread-3, 轮次: 2
pool-1-thread-3, 轮次: 3
pool-1-thread-3, 轮次: 4
pool-1-thread-3线程运行结束
异步执行的结果:1086
main结束

此时虽然main方法的线程结束了,但是线程池中的线程还没有结束,程序还没有停止。