**1. java.util.concurrent.Callable**
Callable 接口与 Runnable 接口都是封装一个异步运行的任务,它们的主要区别如下:
```java
// Runnable不是泛型接口,只有一个 run方法,方法没有返回值
public interface Runnable {
void run();
}
// Callable接口是泛型接口,只有一个call方法,方法有返回值
public interface Callable<V> {
V call() throws Exception;
}
```
**2. java.util.concurrent.Future**
Future 接口可以保存异步计算的结果,启动一个计算后,将 Future 对象交给某个线程,然后忘掉这个 Future 对象,直到将所有的结果计算出来后,再重新获取这个对象,得出最终的结果。
<br/>
就像一个国家的GDP,一个国家的GDP成果是由全国人民共同努力的结果。每个人都有自己的分工,一个人就是一个线程,然后每个人都有一个 Future 容器,每个人把自己工作的成果保存在这个 Future 容器当中,一年结束后,把这些 Future 容器放在一起统计,最终得出全国的GDP数据。
```java
public interface Future<V> {
//调用该方法线程被阻塞,直到计算完成。
//如果中途线程被中断,则抛出InterruptedException
V get() throws InterruptedException, ExecutionException;
//调用该方法超时时,抛出TimeoutException;
//如果中途线程被中断,则抛出InterruptedException
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
// 1. 如果计算尚未开始,调用cancel(true),从此中断线程,不再给重新开始的机会
// 2. 如果计算早已开始,调用cancel(true),中断当前线程,但给重新开始的机会
// 3. 如果参数为false,则不对线程做任何操作
void cancel(boolean mayInterrupt);
//如果对正常运行的线程提前中断,则返回true
boolean isCancelled();
//如果线程正在运行,返回false; 如果已经完成,返回true
boolean isDone();
}
```
<br/>
**3. java.util.concurrent.FutureTask**
FutureTask 包装器实现了 Future 接口和 Runnable 接口。FutureTask使用步骤如下:
```java
//1. 创建一个实现了Callable接口的对象
Callable<Integer> myComputation = new ....;
//2. 调用FutureTask构造器,参数为 Callable 实例
FutureTask<Integer> task = new FutureTask<Integer>(myComputation);
//3. 创建一个线程对象
Thread t = new Thread(task);
//4. 启动线程
t.start();
...
//5. 调用get方法获取最终的计算结果
Integer result = task.get();
```
<br/>
**4. 案例演示**
从一个目录中读取所有的子目录。然后统计出包含某个关键字的文件共有多少个。有多少个文件夹,就启动多少个线程,就有多少个Future对象,计算结果完成后,由第一个被启动的线程来完成统计,得出最终的结果。
```java
public class FutureTest {
public static void main(String[] args) {
try (Scanner in = new Scanner(System.in)) {
System.out.print("请输入一个目录: ");
String directory = in.nextLine();
System.out.print("请输入一个关键字: ");
String keyword = in.nextLine();
MatchCounter counter = new MatchCounter(new File(directory), keyword);
FutureTask<Integer> task = new FutureTask<>(counter);
Thread t = new Thread(task);
t.start(); //从这里启动第一个线程,该线程的名字为Thread-0
try {
//12 matching files.
System.out.println(task.get() + " matching files.");
} catch (ExecutionException e) {
e.printStackTrace();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 此任务计算包含给定关键字的目录及其子目录中的文件
*/
class MatchCounter implements Callable<Integer> {
private File directory;
private String keyword;
public MatchCounter(File directory, String keyword) {
this.directory = directory;
this.keyword = keyword;
}
@Override
public Integer call() {
//在main方法中启动Thread-0后,进入call方法进行计算
System.out.println("......." + Thread.currentThread().getName() + ".......");
int count = 0;
try {
File[] files = directory.listFiles();
List<Future<Integer>> results = new ArrayList<>();
for (File file : files) {
System.out.println(Thread.currentThread().getName() + "->" + file.getName());
if (file.isDirectory()) {
MatchCounter counter = new MatchCounter(file, keyword);
FutureTask<Integer> task = new FutureTask<>(counter);
results.add(task);
Thread t = new Thread(task);
t.start();
System.out.println(Thread.currentThread().getName() + "->" + "Count0=" + count);
} else {
if (search(file)) count++;
System.out.println(Thread.currentThread().getName() + "->" + "Count1=" + count);
}
}
for (Future<Integer> result : results) {
System.out.println(Thread.currentThread().getName() + "->Count2=" + count);
try {
//Thread-0运行到这里,发现Thread-1和Thread-2还没计算完成,于是阻塞Thread-0
//等到Thread-1和Thread-2计算完成后,解除Thread-0的阻塞状态,并由Thread-0统计它们三个的计算结果
count += result.get();
System.out.println(Thread.currentThread().getName() + "->Count3=" + count);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
} catch (InterruptedException e) {
}
return count;
}
/**
* 在文件中搜索给定的关键字
*
* @param file ,被搜索的文件
* @return,返回ture,则表明在文件中包含该关键字
*/
public boolean search(File file) {
try {
try (Scanner in = new Scanner(file, "UTF-8")) {
boolean found = false;
while (!found && in.hasNextLine()) {
String line = in.nextLine();
if (line.contains(keyword)) found = true;
}
return found;
}
} catch (IOException e) {
return false;
}
}
}
```
- 网络通信
- 网络协议
- 端口和套接字
- TCP网络程序
- UDP网络程序
- 多线程聊天室
- 多线程
- 线程相关概念
- 线程实现方式
- 中断线程
- 线程生命周期
- 线程优先级
- 优先级规则
- 案例演示
- 线程同步机制
- 线程同步机制
- synchronized关键字
- ReentrantLock类
- Condition类
- 监视器概念
- volatile关键字
- final变量
- 死锁
- 线程局部变量
- 读/写锁
- 原子类
- 阻塞队列
- 工作规则
- 案例演示
- 常用阻塞队列
- 线程安全集合
- 高效的映射/集/队列
- 并发集视图
- 写数组的拷贝
- Arrays类的并行数组算法
- 同步包装器
- Callable与Future
- 执行器
- 线程池
- 预定执行
- 控制任务组
- Fork-Join框架
- 同步器
- 同步器
- 信号量
- CountDownLatch类
- CyclicBarrier类
- Exchanger类
- SynchronousQueue类
- 线程与Swing
- Swing与线程问题
- 两个原则
- Swing工作线程
- 单一线程规则
- 文件IO
- File类
- 文件输入输出
- ZIP压缩文件
- 集合
- 集合框架
- 集合接口
- 集合实现类
- 线程安全集合
- 集合算法
- 迭代器
- 集合排序
- JDBC
- JDBC是什么
- JDBC-ODBC桥
- JDBC驱动程序类型
- JDBC常用类与接口
- 数据库操作
- 连接数据库
- 增/删/改/查/预处理
- 事务
- 批处理
- commons-dbutils工具
- 安全问题
- Jedis
- 使用Jedis操作Redis数据库
- JSON转换
- 使用连接池
- 案例
- 单例破坏
- 单例定义
- 单例实现方式
- 懒汉式实现单例
- 饿汉式实现单例
- 单例破坏
- 类的单例破坏
- 枚举的单例破坏
- 克隆
- 克隆是什么
- 浅克隆
- 深克隆
- 注解
- 注解是什么
- 三大注解
- 内置注解
- 元注解
- 自定义注解
- NIO
- 相关概念
- BIO/NIO/AIO
- 多线程编程
- 线程同步
- 线程通信
- NIO
- NIO三大核心组件
- NIO网络编程
- NIO文件读写
- AIO
- Java8新特性
- Lambda表达式
- 方法引用
- 函数式接口
- 默认方法
- 什么是默认方法
- 默认方法语法格式
- 多个同名的默认方法问题
- 静态默认方法
- 默认方法实例
- Stream
- Stream是什么
- Stream示例
- Optional容器
- 新的日期时间API
- Base64
- SPI
- SPI是什么
- SPI与API的区别
- 常见场景
- 使用SPI需遵循的约定
- SPI使用步骤