Thread 和 Runnable 的区别:
Thread 才是 Java 里对线程的唯一抽象, Runnable 只是对任务(业务逻辑)的抽象。 Thread 可以接受任意一个 Runnable 的实例并执行。
Callable、 Future 和 FutureTask的关系:
Runnable 是一个接口,在它里面只声明了一个 run()方法, 由于 run()方法返回值为 void 类型,所以在执行完任务之后无法返回任何结果。
public static void main(String[] args) {
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
}
}).start();
}
Callable 位于 java.util.concurrent 包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做 call(), 这是一个泛型接口, call()函数返回的类型就是传递进来的 V 类型。
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Future 就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。
Future接口方法:
因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就有了 FutureTask 实现类。它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。
FutureTask 实现了RunnableFuture接口:
RunnableFuture 接口 继承了 RunnableFuture 继承了 Runnable接口和 Future 接口:
因此在使用Callable 接口来实现线程任务时,可将其封装成一个FutureTask对象,交给Thread执行
程序案例1:
public class CallableTest {
static class myCallable implements Callable<String>{
@Override
public String call() throws Exception {
return Thread.currentThread().getName()+"的执行结果";
}
}
@SneakyThrows
public static void main(String[] args) {
// FutureTask接收一个Callable类
FutureTask<String> futureTask= new FutureTask<>(new myCallable());
// 执行线程任务
new Thread(futureTask).start();
// 获取线程执行后的返回值
String s = futureTask.get();
System.out.println("获取到"+s);
}
}
运行结果:
使用CountDownLatch与Callable 实现对多个线程执行结果的汇总调度
程序案例2:
public class CallableTest {
static CountDownLatch countDownLatch = new CountDownLatch(3);
static class myCallable1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(1000);
countDownLatch.countDown();
return 1;
}
}
static class myCallable2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
countDownLatch.countDown();
return 2;
}
}
static class myCallable3 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
countDownLatch.countDown();
return 3;
}
}
@SneakyThrows
public static void main(String[] args) {
FutureTask<Integer> futureTask1= new FutureTask<>(new myCallable1());
FutureTask<Integer> futureTask2= new FutureTask<>(new myCallable2());
FutureTask<Integer> futureTask3= new FutureTask<>(new myCallable3());
new Thread(futureTask1).start();
new Thread(futureTask2).start();
new Thread(futureTask3).start();
// 阻塞,等待全部线程执行结束
countDownLatch.await();
Integer a = futureTask1.get();
Integer b = futureTask2.get();
Integer c = futureTask3.get();
System.out.println("汇总三个线程的结果:"+(a+b+c));
}
}
执行结果
FutureTask的run方法探究:
我们先来看一段程序:
public class CallableTest {
static class myCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
return 1;
}
}
public static void main(String[] args) {
FutureTask<Integer> futureTask= new FutureTask<>(new myCallable());
IntStream.range(0,30).forEach(l->{
futureTask.run();
});
}
}
上面的程序里,我们调用30次 futureTask.run() 方法,并打印执行该方法的线程
运行结果:
结果发现,我们通过futureTask.run() 调用Callable的 call() 方法只执行了一次,执行该方法的是主线程。我们看下FutureTask的源码
在源码中,我们主要观察两部分,其一是FutureTask中定义的几种描述状态转换的静态变量:
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
NEW:表示一个新的任务,初始状态
COMPLETING:当任务被设置结果时,处于COMPLETING状态,这是一个中间状态。
NORMAL:表示任务正常结束。
EXCEPTIONAL:表示任务因异常而结束
CANCELLED:任务还未执行之前就调用了cancel(true)方法,任务处于CANCELLED
INTERRUPTING:当任务调用cancel(true)中断程序时,任务处于INTERRUPTING状态,这是一个中间状态。
INTERRUPTED:任务调用cancel(true)中断程序时会调用interrupt()方法中断线程运行,任务状态由INTERRUPTING转变为INTERRUPTED
我们接下来重点关注下上面标亮的两种状态
其二是FutureTask的run()方法
public void run() {
// 当一次执行run()方法时,state == NEW 1.1
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
// 首次执行时走try中的逻辑 1.2
try {
// 拿到初始化时的callable 1.3
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 执行了callcable中的call()方法 1.4
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
// 第一次走下面的set()方法 1.5
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
我们来看下set()方法中的逻辑:
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
// 1.6 设置state为NORMAL
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
// 1.7
finishCompletion();
}
}
接下来进入步骤1.7的 finishCompletion()方法
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
// 1.8 置空 callable
callable = null;
}
从上面的第一次执行流程 1.1 ---->1.8 我们看到,在call() 方法执行完之后,主要做了两件事:
- 将state的状态从NEW 更新到了 NORMAL
- 将FutureTask的Callable 对象释放掉,防止因强引用无法正常GC,导致内存泄露
因此当我们再次执行run()方法时就会:
public void run() {
// 当二次执行run()方法时,state == NORMAL 直接return
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
通过上面的源码分析,我们知道,当调用FutureTask.run()方法后,FutureTask中的状态就会随之发生改变,只要状态不再是任务初始状态NEW的时候,我们都无法再次执行。此外,FutureTask.run()的默认执行线程就是当前线程,并不是单独新开启了一个新的线程来执行。在java中只有Thread.start() 方法可以开启多线程。