Runnable和Callable都可以在主线程之外单独启一个线程。
Runnable和Callable的区别在于:
使用方式:Runnable可直接使用 new Thread(Runnable runnable).start();来启动一个线程
Callable需要使用FutureTask进行封装,使用new Thread(new FutureTask<>(Callable callable)).start();来启动一个线程
返回值:Runnable只是单纯的启动一个线程任务,这个线程任务(run方法)不能返回结果给主线程
Callable在启一个线程任务的同时可以通过Future对线程(call方法)的运行结果进行返回
异常处理:Runnable不支持抛出异常,所有异常只能在run方法内消化。
Callable支持异常抛出,可以将call方法内部的异常抛出。指定注意的是抛出的异常并不是抛到.start()方法上去了,而是抛到用来获得返回结果的FutureTask.get()方法上。
阻塞:Runnable不会阻塞主线程的运行。
Callable分两种情况。一种是不获得call方法返回结果即不调用FutureTask.get()的情况,此情况下在阻塞问题上与Runnable无明显区别。另一种情况是需要获得call方法返回结果即调用FutureTask.get()方法的情况,此种情形下如果再运行FutureTask.get()方法时对应的Callable线程还未执行完毕,那么主线程将会被阻塞,直到Callable线程的call方法执行完毕并将结果返回给主线程之后,主线程才会继续执行,因此此种情形下一般使用线程控制类CountDownLatch来控制线程来避免主线程因为一些问题被长时间阻塞。
下面通过一小段代码来验证以上结论:
package sync;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class RunnableAndCallableTest {
public static void main(String[] args) {
long t1 = System.currentTimeMillis();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("This Is Runnable");
}
};
new Thread(runnable).start(); //
long t2 = System.currentTimeMillis();
// 以上代码明显不会运行1s,因此若是运行超过1s则可值阻塞了主线程
System.out.println("runnable是否阻塞主线程:" + String.valueOf((t2 - t1) > 1000));
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000);
System.out.println("This Is Callable");
return "The ruturn of Callable";
}
};
Callable<String> c2 = new Callable<String>() {
@Override
public String call() throws Exception {
int i = 0;
i= 1/i; // 异常向外抛出
return "The return of Exception Callable";
}
};
// 对Callable进行封装
FutureTask<String> task = new FutureTask<>(c);
FutureTask<String> task2 = new FutureTask<>(c2);
long t3 = System.currentTimeMillis();
// 启动线程
try {
new Thread(task).start();
new Thread(task2).start();
System.out.println("异常未抛到start方法上");
} catch (Exception e) {
System.out.println("异常抛到了start方法上");
}
long t4 = System.currentTimeMillis();
// 大于1s的话可以认为主线程阻塞到start方法上了
System.out.println("主线程阻塞在start方法上:" + String.valueOf(t4 - t3 > 1000));
try {
System.out.println(task.get());
System.out.println(task2.get());
System.out.println("异常未抛到了get方法上");
} catch (Exception e) {
System.out.println("异常抛到了get方法上");
}
long t5 = System.currentTimeMillis();
// 大于1s的话可以认为主线程阻塞到get方法上了
System.out.println("主线程阻塞在get方法上:" + String.valueOf(t5 - t4 > 1000));
}
}
执行结果:
runnable是否阻塞主线程:false
异常未抛到start方法上
主线程阻塞在start方法上:false
This Is Runnable
This Is Callable
The ruturn of Callable
异常抛到了get方法上
主线程阻塞在get方法上:true
参考: