logo头像
书院的十三先生

Java并发编程入门(十七)一图掌握线程常用类和接口

一、线程常用类和接口

线程常用类和接口如下:

1.Thread和Runnable是最常用的,区别是一个是类,一个是接口。类的缺点是只能单继承,而接口没有这个限制。

2.Thread和Runnable没有返回值,想得到线程执行结果,要么是通过构造器事先注入存储执行结果的对象,要么是通过生产者-消费者模式得到。为了直接得到线程执行结果,java并发包增加了Callable,不过Callable需要通过ExecutorService来提交执行,而不能简单通过Thread注入后执行。

3.Future是线程池中线程执行后的返回结果,另外通过它可以终止线程执行,判断线程的完成状态等等。

4.通常情况下,使用以上几个接口和类已经足够,FutureTask作为补充,使得可以不用走左边的线程池那条线,就能执行线程并得到线程执行结果,可以理解为是一种轻量级的调用方式,它的调用过程如下:

1
2
3
FetureTask task = new FutureTask(new Callable());  //这里的Callable需要自行实现  
Thread thread = new Thread(task); //这里可以看到FetureTask实现Runnable接口的目的:能被当作线程任务执行。
V v = task.get(); //这里可以看到FetureTask实现Future接口的目的:得到线程任务执行结果。

5.Executors是线程池工厂,生成不同类型的线程池。Executors可能引起内存溢出,有的大厂不推荐使用,而是推荐通过ThreadPoolExecutor人工创建线程池。

6.ExecutorService是线程执行服务,负责执行/提交线程任务。

7.ThreadPoolExecutor用于人工创建线程池,同时也可以执行/提交线程任务。

以上就是线程常用的类和接口,每个都写个demo练习一下,很快就能掌握,使用时也能了然于胸。

二、Executors创建线程池

虽然有的大厂不推荐使用Executors,甚至作为一项代码检查规则给予提示,但是根据实际业务场景需要,如果没有导致内存溢出的场景,还是可以用的。

I、创建固定线程池

可以通过Executors.newFixedThreadPool(nThreads:int)方法来创建固定大小的线程池,然后ExecutorService来提交要执行的线程任务,由于没有限制ExecutorService能提交多少任务,因此可能导致提交任务太多而导致内存溢出。代码示例:

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
public class FixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);

executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made rice.");
}
});

executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made dish.");
}
});

executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " clean up table.");
}
});

executorService.shutdown();
}
}

输入日志:

1
2
3
4
5
pool-1-thread-1 made rice.
pool-1-thread-2 made dish.
pool-1-thread-2 clean up table.

Process finished with exit code 0

日志可以看出,线程池里有两个线程,总共3个任务被执行。

II、创建单线程池

可以通过Executors.newSingleThreadExecutor()来快速创建只有一个线程的线程池,提交到这个线程池里的多个任务只能按照顺序一个个的执行。代码示例:

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 SingleThreadDemo {

public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made rice.");
}
});

executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made dish.");
}
});

executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " clean up table.");
}
});

executorService.shutdown();
}
}

输出日志:

1
2
3
4
5
pool-1-thread-1 made rice.
pool-1-thread-1 made dish.
pool-1-thread-1 clean up table.

Process finished with exit code 0

可以只有一个线程执行多个任务。

III、按计划执行线程池

线程池中的任务可以按照计划执行,代码示例如下:

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
68
69
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
* @ClassName ScheduledThreadPoolDemo
* @Description 按计划执行任务线程池demo
* @Author 铿然一叶
* @Date 2019/10/11 16:13
* @Version 1.0
* javashizhan.com
**/
public class ScheduledThreadPoolDemo {

public static long fixedRateInterval = 0;
public static long lastFixedRateRunTime = System.nanoTime();

public static long withFixedInterval = 0;
public static long lastWithFixedRunTime = System.nanoTime();

public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);

//1秒后执行,只执行1次
executorService.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " made rice.");
}
}, 1, TimeUnit.SECONDS);

//1秒后开始执行,每间隔3秒执行一次,这个间隔时间是从前一个任务【执行开始时间】算起
lastFixedRateRunTime = System.nanoTime();
executorService.scheduleAtFixedRate(new Runnable() {
public void run() {
long runTime = System.nanoTime();
fixedRateInterval = runTime - lastFixedRateRunTime;
lastFixedRateRunTime = runTime;

//模拟任务执行
sleep(2); //这个休眠时间不影响下次执行间隔时间的计算,执行间隔是3秒(当任务执行耗时小于3秒时,如果大于3秒了,则间隔为任务的执行耗时)
System.out.println(Thread.currentThread().getName() + " made dish. Interval:" + fixedRateInterval);
}
}, 1, 3,TimeUnit.SECONDS);

//两秒后执行任务,每次任务之间间隔3秒,这个间隔时间是从前一个任务【执行结束时间】算起
lastWithFixedRunTime = System.nanoTime();
executorService.scheduleWithFixedDelay(new Runnable() {
public void run() {
long runTime = System.nanoTime();
withFixedInterval = runTime - lastWithFixedRunTime;
lastWithFixedRunTime = runTime;

//模拟任务执行
sleep(2); //这个休眠时间会影响下次执行间隔时间的计算,执行间隔是2秒加上本次运行时间
System.out.println(Thread.currentThread().getName() + " clean up table. Interval:" + withFixedInterval);
}
}, 2, 3, TimeUnit.SECONDS);


}

public static void sleep(long millis) {
try {
TimeUnit.SECONDS.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pool-1-thread-1 made rice.
pool-1-thread-2 made dish. Interval:1000771200
pool-1-thread-3 clean up table. Interval:2002448300
pool-1-thread-2 made dish. Interval:3000158800
pool-1-thread-2 made dish. Interval:2999740199
pool-1-thread-1 clean up table. Interval:5001442200
pool-1-thread-2 made dish. Interval:3000925301
pool-1-thread-3 clean up table. Interval:5000781199
pool-1-thread-1 made dish. Interval:2999058400
pool-1-thread-1 made dish. Interval:3000752800
pool-1-thread-2 clean up table. Interval:5001498901
pool-1-thread-1 made dish. Interval:2999442900
pool-1-thread-1 made dish. Interval:3001681600
pool-1-thread-3 clean up table. Interval:5001714800

可以看到:

1.made rice执行了一次

2.clean up table首次2秒后执行,后续间隔为5秒(任务间隔时间+任务执行时间)

3.made dish首次在1秒后执行,后续间隔为3秒(任务间隔时间,没有加上任务执行时间,并且任务执行时间小于间隔时间,所以按照间隔时间执行)

IV、创建可缓存的线程池

通过Executors.newCachedThreadPool()方法可以创建可缓存的线程池,默认池大小为0,最大值为Integer.MAX_VALUE,如果有线程超过60秒未使用就会自动释放。

Executors的每一个创建线程池的方法都是通过ThreadPoolExecutor来构造的,创建缓存线程池调用的构造函数如下:

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

下一章节将介绍ThreadPoolExecutor,这里就不再赘述。

三、ThreadPoolExecutor手动创建线程池

ThreadPoolExecutor手动创建线程池的好处有:

1.可以设置最大线程池大小,支持线程池伸缩。

2.可以使用有界队列来存储待执行的任务,避免任务过多导致内存溢出。

3.可以设置任务拒绝策略,当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize时使用。

如下是ThreadPoolExecutor的构造参数定义:

序号 名称 类型 含义
1 corePoolSize int 核心线程池大小
2 maximumPoolSize int 最大线程池大小
3 keepAliveTime long 线程最大空闲时间,时间单位在下一个参数定义
4 unit TimeUnit 时间单位
5 workQueue BlockingQueue 线程等待队列
6 threadFactory ThreadFactory 线程创建工厂
7 handler RejectedExecutionHandler 拒绝策略

I、代码例子

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
* @ClassName ThreadPoolExecutorDemo
* @Description
* @Author 铿然一叶
* @Date 2019/10/11 16:54
* @Version 1.0
* javashizhan.com
**/
public class ThreadPoolExecutorDemo {

public static void main(String[] args) {

//任务队列容量
final int QUEUE_CAPACITY = 20;
BlockingQueue<Runnable> runnableBlockedQueue = new LinkedBlockingDeque<Runnable>(QUEUE_CAPACITY);

//创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(4,10,60, TimeUnit.SECONDS, (BlockingQueue<Runnable>) runnableBlockedQueue);

int taskNo = 0;
int queueSize = 0;

while (true) {
//随机生成任务数
int taskNum = getTaskNum();

for (int i = 0; i < taskNum; i++) {
taskNo++;

//队列未满则提交新任务
if (queueSize < QUEUE_CAPACITY) {
executor.execute(new Worker("Task" + taskNo));
} else {
//队列满了则等待一会重试, 注意:如果队列满了还提交任务会因为超出队列容量而报错。
while(true) {
sleep(200);
queueSize = executor.getQueue().size();
if (queueSize < QUEUE_CAPACITY) {
executor.execute(new Worker("Task" + taskNo));
break;
}
}
}

queueSize = executor.getQueue().size();
}

}
}

//随机生成一次执行的任务数
private static int getTaskNum() {
return ((int)(1+Math.random()*(8-1+1)));
}

private static void sleep(long millis) {
try {
TimeUnit.MILLISECONDS.sleep(millis);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}

class Worker implements Runnable {

private String name;

public Worker(String name) {
this.name = name;
}

public void run() {
exec();
}

//模拟任务执行
private void exec() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " be called.");
}
}

输出日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Task3 be called.
Task4 be called.
Task2 be called.
Task1 be called.
Task6 be called.
Task5 be called.
Task7 be called.
Task8 be called.
Task9 be called.
Task10 be called.
Task12 be called.
Task11 be called.
Task13 be called.
Task14 be called.
Task15 be called.
Task16 be called.
Task17 be called.
Task18 be called.
Task19 be called.
Task20 be called.
Task21 be called.

以上就是线程常用类和接口了。

end.


站点: http://javashizhan.com/


微信公众号: