Java线程

基础信息

Java语言中有两种方式来创建一个线程:通过继承Thread类,或是实现Runnable接口,并实现其run()方法,通过得到的Thread实例调用start()方法,来启动一个线程。处理器将按照自己的策略来分配每个线程的执行,因此线程的执行顺序、时间等是不确定的,如果一个任务依赖于有序的执行,需要自行进行同步之类的操作。另外在创建了一个Thread后,在它的任务执行完毕之前,它将一直存在且垃圾回收器无法回收它。

线程的创建和销毁需要较大的开销,同时当显示的创建一个线程并启动它时,可能会由于代码中的缺陷,导致系统创建了大量的线程导致系统崩溃,在这里可以使用Executor来启动线程。Executor可以接收一个Thread或Runnable对象并启动它们,Executor常用的实现类型有CachedThreadPool, FiexdThreadPool,SingleThreadExecutor等,其中 FiexdThreadPool 线程池将创建一个固定大小的线程池,SingleThreadPool则创建单个线程的线程池;每一种类型的线程池都将在可能的情况下复用线程。

线程是没有返回值的,当一个线程启动过后,它将独立于启动它的线程;线程中的任何异常也无法被父线程捕捉到。因此一个线程启动后,要确保它能自行完成任务并处理期间发生的异常。如果需要执行后得到返回值,可以使用Callable来代替Runnable,实现其中的call()方法,并且,必须使用ExecutorService.submit()来调用它。使用实例如下:

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
package io.lovs.java.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
* Created by madman on 2017/4/18.
* Callable使用示例
*/
public class TaskLearn {
public static void main(String[] args){
ExecutorService es = Executors.newCachedThreadPool();
List<Future<String>> futures = new ArrayList<>();
for(int i=0; i<10; i++){
futures.add(es.submit(new TaskHasResult(i)));
}

for(Future<String> f: futures){
try {
System.out.println(f.get());

} catch (InterruptedException e) {
e.printStackTrace();
return;
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}


class TaskHasResult implements Callable<String> {

private int index;
public TaskHasResult(int index){
this.index = index;
}

@Override
public String call() throws Exception {

return "Task index is " + index;
}
}

submit()方法调用后将返回一个Future对象,通过调用它的get()方法可以得到call()中的返回值。调用get()的时候,方法将被阻塞,直到得到结果为止,可以通过调用isDone()来判断是否已经执行完毕。

线程中的异常

Java中的线程无法向外部抛出异常,简单来说,线程内的一切异常都不能在线程外部catch到,因为Java中的线程的理念是:它们是一个完整独立的个体,能够自行处理完它的任务,包括其中的异常。因此在线程中,异常需要在内部处理,否则将会把错误传播到控制台,并可能终止线程。Java SE5后,为这个问题新增了解决办法:Thread.UncaughtExceptionHandler接口,它将每个Thread对象都附带一个异常处理器,其中的uncaughtException()方法将会在线程因未捕获的异常而要终止时被调用。
下面的代码展示了它的简单用法:

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
package io.lovs.java.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

/**
* Created by madman on 2017/5/22.
* 线程捕获异常的方式
*/
public class ThreadCatchException {

public static void main(String[] args){
//通过为ExecutorService设置ThreadFactory的方式
ExecutorService es = Executors.newCachedThreadPool(new ExceptionThreadFactory());
es.execute(new ExceptionThread());

//直接在线程中设置
Thread t2 = new Thread(new ExceptionThread());
t2.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
t2.start();
//直接通过ThreadFactory创建线程
Thread t3 = new ExceptionThreadFactory().newThread(new ExceptionThread());
t3.start();
}

}

/**
* 抛出异常的线程
*/
class ExceptionThread implements Runnable{

@Override
public void run() {
throw new RuntimeException();
}
}

/**
* ThreadFactory来为线程设置异常处理器<br>
* 可用于线程池当中
*/
class ExceptionThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
return t;
}
}

/**
* 实现自己的异常处理器
* 在uncaughtException方法中捕获并处理异常
*/
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{


@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught exception");
e.printStackTrace();
}
}

上面显示了几种方式来使用异常处理器,当直接创建线程并启动时,可以在线程对象上设置该异常处理器。如果是通过线程池来执行线程,可以创建一个自己的ThreadFactory,它的作用就是为每一个新创建的线程附上一个异常处理器。

ThreadFactory即为工厂模式的一种运用,通过这个可以在生成一个线程时对齐进行必要的操作,免去每个线程单独去设置的麻烦。