15个顶级Java多线程面试题及回答 http://ifeve.com/15-java-faq/
(1)join()方法:能够使线程顺序执行。可以将一个任务分为多个子任务,调用join()方法等待这些子任务完成运算,然后汇总结果。需要注意这并不能保证各个子任务线程的结束顺序。
(2)线程只能从就绪状态(runnable)这一条唯一路径到运行状态(running)
(3)Thread类,定义了一些列的线程操作函数。例如,sleep()休眠, interrupt()中断, getName()获取线程名称等
(4)在启动线程时调用start()方法,重复调用会抛出线程状态异常。调用run()就相当于调用了普通方法
(5)在线程调用wait()方法进入阻塞状态,再次被唤醒时继续执行wait()方法后面的代码
(6)wait()会释放锁,而yield()和sleep()不会释放锁
(7)注意interrupt()方法被调用后状态由等待blocked进入锁定blocked,这在AQS中的公平锁中需要处理这种情况。
(1)public void interrupt() 中断目标线程
(2)public boolean isInterrupted() 返回中断状态
(3)public static boolean interrupted() 静态方法,清除中断状态并返回之前的值
阻塞库方法,Thread.sleep()和Object.wait()等,都会检查线程何时中断,并且发现中断时提前返回,它们在响应中断时执行的操作包括:清除中断状态、抛出InterruptedException,表示阻塞操作由于中断而提前结束。
可以总结下:
(1)处于blocked状态的线程:清除中断状态、抛出InterruptedException,表示阻塞操作由于中断而提前结束
(2)处于runnable或者running状态时,线程的中断标记会被设置为true
(3)处于dead状态时不会产生任何操作
上面的题目一定要看,我在这里筛选及补充了几个题目:
把相互独立的计算任务包含在一个工作单元内,无需为每个单元启动新的线程。这样处理多线程代码通常效率更高。因为不用去为每个计算单元单独启动Thread线程。执行代码的线程是重用的。
(1)任务
Callable代表了一段可以调用并返回结果的代码
Future接口用来表示异步任务,是还没有完成的任务给出的未来结果。主要方法有get()、cancel()和isDone()
FutureTask是Future接口的常用实现类,它也实现了Runnable接口,所以和Runnable和Callable一样,可以由执行者高度。
(2)执行者 通过Executors类的工厂方法获取众多执行者之一
有个关于FutureTask的好例子,如下:
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
|
public
class
FutureTaskExample {
public
static
void
main(String[] args) {
MyCallable callable1 =
new
MyCallable(
1000
);
// 要执行的任务
MyCallable callable2 =
new
MyCallable(
2000
);
FutureTask
new
FutureTask
// 将Callable写的任务封装到一个由执行者调度的FutureTask对象
FutureTask
new
FutureTask
ExecutorService executor = Executors.newFixedThreadPool(
2
);
// 创建线程池并返回ExecutorService实例
executor.execute(futureTask1);
// 执行任务
executor.execute(futureTask2);
while
(
true
) {
try
{
if
(futureTask1.isDone() && futureTask2.isDone()){
// 两个任务都完成
System.out.println(
"Done"
);
executor.shutdown();
// 关闭线程池和服务
return
;
}
if
(!futureTask1.isDone()){
// 任务1没有完成,会等待,直到任务完成
System.out.println(
"FutureTask1 output="
+futureTask1.get());
}
System.out.println(
"Waiting for FutureTask2 to complete"
);
String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
if
(s !=
null
){
System.out.println(
"FutureTask2 output="
+s);
}
}
catch
(InterruptedException | ExecutionException e) {
e.printStackTrace();
}
catch
(TimeoutException e){
//do nothing
}
}
}
}
|
示例如下:
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
|
public
class
Sums {
// 使用Callable比Runnable更优势的地方在于Callable可以有确切的返回值。
static
class
Sum
implements
Callable
private
final
long
from;
private
final
long
to;
Sum(
long
from,
long
to) {
this
.from = from;
this
.to = to;
}
@Override
public
Long call() {
long
acc =
0
;
for
(
long
i = from; i <= to; i++) {
acc = acc + i;
}
return
acc;
}
}
public
static
void
main(String[] args)
throws
Exception {
ExecutorService executor = Executors.newFixedThreadPool(
2
);
// Executes the given tasks, returning a list of Futures holding their status and results when all complete
List
asList(
new
Sum(
0
,
10
),
new
Sum(
100
, 1_000),
new
Sum(10_000, 1_000_000)
));
// 另外要注意executor服务必须被关闭。如果它没有被关闭,主方法执行完后JVM就不会退出,因为仍然有激活线程存在
executor.shutdown();
for
(Future
// Waits if necessary for the computation to complete, and then retrieves its result.
System.out.println(result.get());
}
}
}
|
书写如上程序时需要注意三点:
(1)Callable是接口,我们需要重写的方法为call()
(2)线程池用完一定要shutdown()
关于线程池的实现原理可以参考如下:
(1)一定要看 http://www.jianshu.com/p/87bff5cc8d8c
(1)http://blog.csdn.net/mazhimazh/article/details/19243889
(2)http://blog.csdn.net/mazhimazh/article/details/19283171
(3)参考《Java特种兵》295页内容
线程池实现原理就是线程池与工作队列的组合,在Executor任务执行框架中就体现了这种模式。
一个简单的小例子。
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
package
ThreadPool;
import
java.util.LinkedList;
import
java.util.List;
/**
* 线程池类,线程管理器:创建线程,执行任务,销毁线程,获取线程基本信息
*/
public
final
class
ThreadPool {
// 线程池中默认线程的个数为5
private
static
int
worker_num =
5
;
// 工作线程
private
WorkThread[] workThrads;
// 未处理的任务
private
static
volatile
int
finished_task =
0
;
// 任务队列,作为一个缓冲,List线程不安全
private
List
new
LinkedList
private
static
ThreadPool threadPool;
// 创建具有默认线程个数的线程池
private
ThreadPool() {
this
(
5
);
}
// 创建线程池,worker_num为线程池中工作线程的个数
private
ThreadPool(
int
worker_num) {
ThreadPool.worker_num = worker_num;
workThrads =
new
WorkThread[worker_num];
for
(
int
i =
0
; i < worker_num; i++) {
workThrads[i] =
new
WorkThread();
workThrads[i].start();
// 开启线程池中的线程
}
}
// 单态模式,获得一个默认线程个数的线程池
public
static
ThreadPool getThreadPool() {
return
getThreadPool(ThreadPool.worker_num);
}
// 单态模式,获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数
// worker_num<=0创建默认的工作线程个数
public
static
ThreadPool getThreadPool(
int
worker_num1) {
if
(worker_num1 <=
0
)
worker_num1 = ThreadPool.worker_num;
if
(threadPool ==
null
)
threadPool =
new
ThreadPool(worker_num1);
return
threadPool;
}
// 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定
public
void
execute(Runnable task) {
synchronized
(taskQueue) {
taskQueue.add(task);
taskQueue.notify();
}
}
// 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定
public
void
execute(Runnable[] task) {
synchronized
(taskQueue) {
for
(Runnable t : task)
taskQueue.add(t);
taskQueue.notify();
}
}
// 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定
public
void
execute(List
synchronized
(taskQueue) {
for
(Runnable t : task)
taskQueue.add(t);
taskQueue.notify();
}
}
// 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public
void
destroy() {
while
(!taskQueue.isEmpty()) {
// 如果还有任务没执行完成,就先睡会吧
try
{
Thread.sleep(
10
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
// 工作线程停止工作,且置为null
for
(
int
i =
0
; i < worker_num; i++) {
workThrads[i].stopWorker();
workThrads[i] =
null
;
}
threadPool=
null
;
taskQueue.clear();
// 清空任务队列
}
// 返回工作线程的个数
public
int
getWorkThreadNumber() {
return
worker_num;
}
// 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成
public
int
getFinishedTasknumber() {
return
finished_task;
}
// 返回任务队列的长度,即还没处理的任务个数
public
int
getWaitTasknumber() {
return
taskQueue.size();
}
// 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数
@Override
public
String toString() {
return
"WorkThread number:"
+ worker_num +
" finished task number:"
+ finished_task +
" wait task number:"
+ getWaitTasknumber();
}
/**
* 内部类,工作线程
*/
private
class
WorkThread
extends
Thread {
// 该工作线程是否有效,用于结束该工作线程
private
boolean
isRunning =
true
;
/*
* 关键所在啊,如果任务队列不空,则取出任务执行,若任务队列空,则等待
*/
@Override
public
void
run() {
Runnable r =
null
;
while
(isRunning) {
// 注意,若线程无效则自然结束run方法,该线程就没用了
synchronized
(taskQueue) {
// 在这里提供了同步
while
(isRunning && taskQueue.isEmpty()) {
// 队列为空
try
{
taskQueue.wait(
20
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
}
if
(!taskQueue.isEmpty())
r = taskQueue.remove(
0
);
// 取出任务
}
if
(r !=
null
) {
r.run();
// 执行任务
}
finished_task++;
r =
null
;
}
}
// end run
// 停止工作,让该线程自然执行完run方法,自然结束
public
void
stopWorker() {
isRunning =
false
;
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//测试线程池
public
class
TestThreadPool {
public
static
void
main(String[] args) {
// 创建3个线程的线程池
ThreadPool t = ThreadPool.getThreadPool(
3
);
t.execute(
new
Runnable[] {
new
Task(),
new
Task(),
new
Task() });
t.execute(
new
Runnable[] {
new
Task(),
new
Task(),
new
Task() });
System.out.println(t);
t.destroy();
// 所有线程都执行完成才destory
System.out.println(t);
}
// 任务类
static
class
Task
implements
Runnable {
private
static
volatile
int
i =
1
;
@Override
public
void
run() {
// 执行任务
System.out.println(
"任务 "
+ (i++) +
" 完成"
);
}
}
}
|
Java线程的调度分为协同式线程调度和抢占式线程调度。
ScheduleThreadPoolExecutor是Java提供的多线程调度器,它可以接收任务,并把它们安排给线程池里的线程。可以参考如下内容:
《Java特种兵》 306页
利用了多线程加上任务资源共享的方式来实现服务器端大量任务的调度。