这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战
前言
之前介绍了创建线程的四种方式今天来仔细的讲一讲Callable接口
创建线程的四种方式
- 继承Thread
public class Thread_ {
public static void main(String[] args) {
Data1 data1 = new Data1(); // 创建对象
data1.start(); // 开启线程
}
}
/**
* 继承Thread类,重写run方法
*/
class Data1 extends Thread{
@Override
public void run() {
System.out.println("线程内方法");
}
}
- 实现Runnable接口
public class Thread_ {
public static void main(String[] args) {
Data1 data1 = new Data1(); // 创建对象
new Thread(data1).start(); // 将对象放入构造方法中,并开启线程
}
}
/**
* 实现Runnable接口,重写run方法
*/
class Data1 implements Runnable{
@Override
public void run() {
System.out.println("线程内方法");
}
}
- 实现Callable接口
public class Thread_ {
public static void main(String[] args) {
Data1 data1 = new Data1(); // 创建对象
FutureTask<String> futureTask = new FutureTask<String>(data1); // 创建 futureTask对象将实现Callable的类的对象放到构造方法中
new Thread(futureTask).start(); // 将对象放入构造方法中,并开启线程
}
}
/**
* 实现Callable接口,重写run方法
*/
class Data1 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("线程内方法");
return "11";
}
}
4. 使用线程池
public class Test1 {
public static void main(String[] args) {
// 单个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 单个线程
try{
for (int i = 1; i <= 5; i++) {
// 使用线程池,来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
Callable的使用
可以看出除了使用线程池方法外我们都需要通过手动使用Thread类来开启线程
那么我们来看一下Thread类的构造方法
可以看到我们需要传入Runnable接口实现类或者不传参
那么Callable接口和Runnable接口并无直接的关联,要怎么使用呢?
解决方法就在下面的类图中
我们可以看到 RunnableFuture接口继承了Runnable接口,而它有一个实现类为FutureTask,其构造方法可以传入Callable接口,这样Runnable接口就和Callable接口就存在了关系
Callable接口的优点
class MyThread implements Callable<String>{
// 泛型为返回的数据的类型
@Override
public String call() throws Exception {
return "rer";
}
}
从上面Callable接口的自定义实现类来看,Callable的接口相比较Runnable接口存在以下几点优点
可以有返回值
可以抛出异常
还有一些细节是要注意的
- FutureTask 会判断线程状态,所以只会被线程run一次
- 因为要先执行线程里的方法,所以获取返回值可能会被阻塞