最近再次阅读了java并发编程的相关书籍和博客,以此为机会对自己学习到的知识做一个总结。
创建线程
Runnable 和 Thread
很多博客说创建线程有两种方式一种是通过java.lang.Thread
来实现,一种是实现接口java.lang.Runnable
。对此有我不同的看法。我认为创建线程只有一种方式那就是通过java.lang.Thread
来实现。接口java.lang.Thread
只是编写了线程的执行体。最终创建线程还是需要通过Thread来实现。
使用Runnable的例子
- 实现Runnable接口
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable 线程ID="+Thread.currentThread().getId());
}
}
- 调用Runnable
System.out.println("调用者 线程ID="+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
- 通过Thread来调用Runnable
System.out.println("调用者 线程ID="+Thread.currentThread().getId());
new Thread(runnable).start();
通过打印信息可以知道直接调用Runnable的方法,调用Runnable和Runnable的run方法在同一个线程,线程ID相同。而通过Thread来调用Runnable,调用者和Runnable的run方法在不同的线程,线程ID不相同。因此可知,Runnable接口只是线程的执行体,创建线程只有一个方式就是通过Thread的方式。
查看Thread的源码
public class Thread implements Runnable{
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
可以这么理解,Runnable的run方法是执行主体,Thread的功能是创建一个线程,在线程中调用run方法。即在线程中执行函数。
Thread 中start和Run的区别
在很多面试或者笔试中,会问Thread中start和Run的区别。首先run的本质就是执行Runnable(target是Runnable的实例)的run方法,就是普通的调用函数,因此就能够理解run方法和调用者是同一个线程,run方法可以重复多次调用,run方法会阻塞当前线程,只要把run当做普通的调用实例中的方法就可以很好的理解了。
那么start呢。查看start的源码
public synchronized void start() {
...
group.add(this);
boolean started = false;
try {
start0();
started = true;
}
...
}
private native void start0();
从简要的代码可以确认,start会修改状态,核心的是调用native方法实现线程的调用。因此star()方法是用于创建线程,不能被多次调用的就好理解了。
Callable,Future 相关类
将Runnable理解为线程的执行体,那么Callable,Future相关的类就好理解了。
既然有了Runnable这个线程的执行体,可以在线程中调用相关方法了为什么要设计Callable,Future相关的类呢,这个要从Runnable存在的问题来理解。
- Runnable 中的run 方法无返回值,因此无法获知执行体的执行结果。
- Runnable 中的run 方法只能在run中处理异常,无法将异常抛给调用者。
- Runnable 中无相关接口,修改和观察线程执行状态。
注意
以下所说的执行体是Callable中的call方法。
根据Runnable 中存在的问题,就可以设计一个接口或者类来解决这些问题。因此可以把接口Future看做是Runnable的功能扩展。Future的源码如下:
public interface Future<V>{
// 提供接口获取执行的执行结果 两个get的区别只是增加了超时机制
// 通过在获取结果的过程中抛出异常
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
// 提供接口中断线程,即中断执行体的执行
boolean cancel(boolean mayInterruptIfRunning);
// 提供接口观察执行的执行状态
boolean isCancelled();// 是否被取消
boolean isDone(); // 是否执行完成
}
因为Threa的执行体,一定是实现了Runnable接口的因此,需要将Runnable和Future进行一个结合,形成了RunnableFuture。RunnableFuture的源码如下
public interface RunnableFuture<V> extends Runnable, Future<V>{
void run();
}
扩展功肯定需要一个类来实现,因此就有了FutureTask。
获取执行的执行结果
下面着重分析FutureTask,看源码是如何解决以上问题的。
- 解决第一问题,获取执行体的执行结果。
这时候要把线程的执行体放在一个有返回值,可以跑出异常的方法中,然后在run中调用这个方法,就能获取到执行体的执行结果。
Callable的源码
public interface Callable<V> {
V call() throws Exception;
}
线程执行的时候运行run方法。这时候会调用我们定义好的执行体Callable。
public class FutureTask<V> implements RunnableFuture<V>{
// 线程运行的时候执行这个方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
// 出现callable
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 获取callable
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 保存异常
setException(ex);
}
if (ran)
set(result);//保存结果
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
保存执行结果
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = v;// 保存执行结果
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
小结
为了获取到执行体的执行结果,将执行体由原来的实现Runnable中的run方法,改为实现Callable中的Call方法。为了能够复用原来已经设计好的Thread,保证Thread线程执行方法时能够调用到Callable的call方法。因此在run中想一些办法执行到Call方法,因此有了FutureTask中的Run中调用Callable中的Call方法。
获取执行体抛出的异常
线程Thread的执行主体是run方法,run方法是无法抛出异常的,因此需要将异常进行保存。参见FutureTask的run方法,就是在抛出异常的时候,调用函数setException保存了执行体(此时为Callable中的call方法)抛出的异常,在调用者调用get的时候可以获取到异常。这样做的好处也是保证了异常可以在想要获取到的地方获取。源码如下
- 保存异常
protected void setException(Throwable t) {
// 比较状态
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
// 保存异常
outcome = t;
U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
finishCompletion();
}
}
- 获取异常
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);// 阻塞等待执行体执行完成,内部有超时等待机制,中断异常,
return report(s); // 返回结果或者抛出异常
}
// 抛出执行体中的异常,或者返回执行执行结果
private V report(int s) throws ExecutionException {
// outcome 如果执行体正常执行完成,是执行体的执行结果,如果执行体抛出异常,outcome保存的是异常
Object x = outcome;
// 执行体正常执行完成,返回结果
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
// 执行体抛出异常,结束执行,抛出异常
throw new ExecutionException((Throwable)x);
}
- 将FutureTask 和 Thread 结合实现线程
class FirstCallable implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 0; i< 3;i++){
if (i == 2) {
throw new RuntimeException("抛出异常");
}
System.out.println("FirstCallable "+i);
try {
Thread.sleep( 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "success";
}
}
private void doSomething() {
}
public void funcTest1() {
FutureTask<String> futureTask = new FutureTask<>(new FirstCallable());
new Thread(futureTask).start();
// 假设函数执行耗时操作,在操作的过程中FutureTask 已抛出异常
// 执行过程中不需关心 FutureTask 的执行情况
doSomething();
try {
// 在获取执行体执行结果的时候,如果FutureTask有异常,这时候可以捕获到
String result = futureTask.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
获取执行体的执行状态
原Runnable接口,是无法通过其接口控制run方法的执行,比如说想停止执行体的执行等等,后面有一小结,专门将这个问题。Future 为了解决这个问题提供了专门的接口
public interface Future<V>{
// 取消执行体的执行
boolean cancel(boolean mayInterruptIfRunning);
// 获取执行体是否取消
boolean isCancelled();
// 获取执行体是否执行完成(由于这个方法是非阻塞的,可以通过这个接口来遍历,如果执行体执行完成,再去获取结果)
boolean isDone()
}
FutureTask 是如何实现Future的接口的
- 停止执行体的执行
public class FutureTask<V> implements RunnableFuture<V> {
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();// 本质还是通过interrupt来停止线程
} finally { // final state
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
}
通过源码可以看出,FutureTask 中断执行体的执行,是通过接口interrupt来设置线程的状态,并没有直接中断执行体的执行,因此在执行体中需要通过接口isInterrupted接口来判断当前现在的状态,以此来决定执行体是否继续执行。
示例如下
class FirstCallable implements Callable<String>{
@Override
public String call() throws Exception {
for(int i = 0;i< 1000;i ++) {
if (!Thread.currentThread().isInterrupted()) {
// 当前的线程已经被外部停止,这时可以中断执行体的执行。
break;
}
System.out.println("number = "+i);
}
return "success";
}
}
获取执行体的执行状态
源码如下
public boolean isCancelled() {
return state >= CANCELLED;
}
public boolean isDone() {
return state != NEW;
}
通过isCancelled和isDone的实现可以获知,这两个接口返回的是FutureTask 的状态,并不能真实的反应执行体的执行状态,因此执行体需要特别注意,监听FutureTask 的状态或者Thread(线程的状态),通过这些状态,执行体执行相应的操作,保证执行体和FutureTask 是同一状态。
小结
最后通过一个类图,总结一下关于线程创建的问题。