创建线程的方式
Java 中想要创建一个线程,比较容易的两种实现方式是继承 Thread 类或者实现 Runnable 接口。
以实现 Runnable 接口为例:
public class MyRunnableThread implements Runnable{
@Override
public void run() {
System.out.println("子线程开始执行");
}
public static void main(String[] args) {
// 创建线程对象并启动
new Thread(new MyRunnableThread()).start();// 执行后输出:子线程开始执行
}
}
上边这段代码比较简单,但存在个问题,它的 run 方法没有返回值。如果我们想要获取返回值,还需要做一些额外的操作,比如下边的代码:
public class MyRunnableThread implements Runnable{
/**
* 用来保存 run 方法的返回结果
*/
private volatile String result;
@Override
public void run() {
System.out.println("子线程开始执行");
try {
// 假设执行一些任务花费的时长
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 执行完后,将结果保存到 result 中
result = "OK";
}
/**
* 获取返回结果
* @return
*/
public String getResult(){
while(true){
if(null != result){
// 有结果后返回
return result;
}
// 让出一下 CPU 时间片,过会再来看看
Thread.yield();
}
}
public static void main(String[] args) {
// 创建线程对象并启动
MyRunnableThread myRunnableThread = new MyRunnableThread();
new Thread(myRunnableThread).start();
long start = System.currentTimeMillis();
System.out.println("getResult 返回结果:"+myRunnableThread.getResult());
long end = System.currentTimeMillis();
System.out.println("getResult 花费的时间:" + (end - start));
}
}
main 方法执行后输出:
子线程开始执行
getResult 返回结果:OK
getResult 花费的时间:3008
如上边这段代码,如果想要一个返回结果,那么就需要将结果保存到变量,还要提供额外的方法读取,非常不便。
什么是 Callable 和 Future
Callable 接口类似于 Runnable 接口, Runnable 没有返回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执行后,可以有返回值:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。可以认为 Callable 是带有返回值的 Runnable 。
而 Future 类型的实例代表一个未来能获取结果的对象。所以说 Callable 用于产生结果,Future 用于获取结果。
通过 Callable 和 Future 来创建有返回值的线程
public class MyCallableThread implements Callable {
@Override
public Object call() throws Exception {
System.out.println("子线程开始执行");
// 假设执行一些任务花费的时长
Thread.sleep(3000);
return "OK";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建 Callable 任务
Callable callable = new MyCallableThread();
// 将 Callable 任务提交给线程池去执行并拿到 Future
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<String> future = executor.submit(callable);
long start = System.currentTimeMillis();
// 从 Future 获取异步执行返回的结果
System.out.println("返回结果:" + future.get());
long end = System.currentTimeMillis();
System.out.println("获取返回结果花费时间:" + (end-start));
// 关闭线程池
executor.shutdown();
}
}
main 方法执行后输出:
子线程开始执行
返回结果:OK
获取返回结果花费时间:3002
上边这段代码就简单多了:
- 创建 Callable 类型的对象;
- 创建线程池;
- 将 Callable 类型对象提交给线程池执行,并得到 Future 对象;
- 获取结果。
这里在调用 Future 的 get() 方法时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么 get() 会阻塞,直到任务完成后才返回结果。
除了 get() 方法,Future 中还定义了其它一些方法:
boolean cancel(boolean mayInterruptIfRunning);
取消当前任务;boolean isCancelled();
判断任务是否被取消boolean isDone();
判断任务是否已完成V get();
获取结果(可能会等待)V get(long timeout, TimeUnit unit);
获取结果,但只等待指定的时间;
什么是 FutureTask
FutureTask 是 Future 接口的实现类,也可以代表异步计算的结果。除了实现 Future 接口外,FutureTask 还实现了 Runnable 接口,因此,FutureTask 可以交给线程池去执行,也可以由调用线程直接执行。
FutureTask 有两个构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
第一个构造方法传入的是一个 Callable 类型的对象,第二个构造方法传入的是一个 Runnable 类型的对象,但在内部又被 Executors 工厂类包装成了 Callable 类型对象。
通过 Callable 和 FutureTask 来创建有返回值的线程
public class MyCallableThread implements Callable {
@Override
public Object call() throws Exception {
System.out.println("子线程开始执行");
// 假设执行一些任务花费的时长
Thread.sleep(3000);
return "OK";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建 Callable 任务
Callable callable = new MyCallableThread();
// 创建 FutureTask 任务
FutureTask futureTask = new FutureTask(callable);
// 开启线程去执行 FutureTask 任务
new Thread(futureTask).start();
long start = System.currentTimeMillis();
// 从 Future 获取异步执行返回的结果
System.out.println("返回结果:" + futureTask.get());
long end = System.currentTimeMillis();
System.out.println("获取返回结果花费时间:" + (end-start));
}
}
main 方法执行后输出:
子线程开始执行
返回结果:OK
获取返回结果花费时间:3002
案例示范
假如我们调一个下单接口,接口中需要查询客户信息(耗时1s)、商品信息(耗时2s)、账户信息(耗时4s)等,如果按照串行化执行,执行完后,总共耗时:7s (1+2+4)。
示例代码如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
// 查询客户信息
Object customer = getCustomer();
// 查询商品信息
Object goods = getGoods();
// 查询账户信息
Object account = getAccount();
System.out.println("客户信息:" + customer);
System.out.println("商品信息:" + goods);
System.out.println("账户信息:" + account);
long end = System.currentTimeMillis();
System.out.println("花费时长:" + (end - start));
}
/**
* 模拟查询客户信息
*/
public static Object getCustomer() throws InterruptedException {
// 假设查询需要花费1s
Thread.sleep(1000);
return "customerData";
}
/**
* 模拟查询商品信息
*/
public static Object getGoods() throws InterruptedException {
// 假设查询需要花费2s
Thread.sleep(2000);
return "goodsData";
}
/**
* 模拟查询账户信息
*/
public static Object getAccount() throws InterruptedException {
// 假设查询需要花费4s
Thread.sleep(4000);
return "accountData";
}
}
main 方法执行后输出:
客户信息:customerData
商品信息:goodsData
账户信息:accountData
花费时长:7002
我们再来通过有返回值的多线程试下:
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
// 创建一个定长线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 将查询操作提交给线程池执行
Future<Object> customerFuture = executor.submit(Test::getCustomer);
Future<Object> goodsFuture = executor.submit(Test::getGoods);
Future<Object> accountFuture = executor.submit(Test::getAccount);
// 获得客户信息
Object customer = customerFuture.get();
// 获得商品信息
Object goods = goodsFuture.get();
// 获得账户信息
Object account = accountFuture.get();
System.out.println("客户信息:" + customer);
System.out.println("商品信息:" + goods);
System.out.println("账户信息:" + account);
long end = System.currentTimeMillis();
// 关闭线程池
executor.shutdown();
System.out.println("花费时长:" + (end - start));
}
/**
* 模拟查询客户信息
*/
public static Object getCustomer() throws InterruptedException {
// 假设查询需要花费1s
Thread.sleep(1000);
return "customerData";
}
/**
* 模拟查询商品信息
*/
public static Object getGoods() throws InterruptedException {
// 假设查询需要花费2s
Thread.sleep(2000);
return "goodsData";
}
/**
* 模拟查询账户信息
*/
public static Object getAccount() throws InterruptedException {
// 假设查询需要花费4s
Thread.sleep(4000);
return "accountData";
}
}
main 方法执行后输出:
客户信息:customerData
商品信息:goodsData
账户信息:accountData
花费时长:4058
通过输出可以看出,时间只花费了 4s。这比较容易理解,因为并行的时候,花费的时间只跟耗时最长的线程有关。