欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发笔记(九十八)利用Callable启动线程

程序员文章站 2022-10-06 08:23:07
前面介绍了如何利用Runnable接口构建线程任务,该方式确实方便了线程代码的复用与共享,然而Runnable不像公共方法那样有返回值,也就无法将线程代码的处理结果传给外部,造成外部既不知晓该线程是否已经执行完毕,也不了解该线程的运算结果是什么,总之无法跟踪分线程的行动踪迹。这里显然是不完美的,调用 ......

前面介绍了如何利用runnable接口构建线程任务,该方式确实方便了线程代码的复用与共享,然而runnable不像公共方法那样有返回值,也就无法将线程代码的处理结果传给外部,造成外部既不知晓该线程是否已经执行完毕,也不了解该线程的运算结果是什么,总之无法跟踪分线程的行动踪迹。这里显然是不完美的,调用方法都有返回值,为何通过runnable启动线程就无法获得返回值呢?为此java又提供了另一种开启线程的方式,即利用callable接口构建任务代码,实现该接口需要重写call方法,call方法类似run方法,同样存放着开发者定义的任务代码。不同的是,run方法没有返回值,而call方法支持定义输出参数,从而在设计上保留了分线程在运行结束时返回结果的可能性。
callable是个泛型接口,它的返回值类型在外部调用时指定,要想创建一个callable实例,既能通过定义完整的新类来实现,也能通过普通的匿名内部类实现。举个简单的应用例子,比如希望开启分线程生成某个随机数,并将随机数返回给主线程,则采取匿名内部类方式书写的callable定义代码如下所示:

		// 定义一个callable代码段,返回100以内的随机整数
		// 第一种方式:采取匿名内部类方式书写
		callable<integer> callable = new callable<integer>() {
			@override
			public integer call() { // 返回值为integer类型
				int random = new random().nextint(100); // 获取100以内的随机整数
				// 以下打印随机数日志,包括当前时间、当前线程、随机数值等信息
				printutils.print(thread.currentthread().getname(), "任务生成的随机数="+random);
				return random;
			}
		};

 

由于callable又是个函数式接口,因此能够利用lambda表达式来简化匿名内部类,于是掐头去尾后的lambda表达式代码示例如下:

		// 第二种方式:采取lambda表达式书写
		callable<integer> callable = () -> {
			int random = new random().nextint(100);
			printutils.print(thread.currentthread().getname(), "任务生成的随机数="+random);
			return random;
		};

 

因为获取随机数的关键代码仅有一行,所以完全可以进一步精简lambda表达式的代码,压缩冗余后只有下面短小精悍的一行代码:

		// 第三种方式:进一步精简后的lambda表达式
		callable<integer> callable = () -> new random().nextint(100);

 

有了callable实例之后,还需要引入未来任务futuretask把它包装一下,因为只有futuretask才能真正跟踪任务的执行状态。以下是futuretask的主要方法说明:
run:启动未来任务。
get:获取未来任务的执行结果。
isdone:判断未来任务是否执行完毕。
cancel:取消未来任务。
iscancelled:判断未来任务是否取消。
现在结合callable与futuretask,串起来运行一下拥有返回值的未来任务,首先把callable实例填进futuretask的构造方法,由此得到一个未来任务的实例;接着调用未来任务的run方法启动该任务,最后调用未来任务的get方法获取任务的执行结果。根据以上步骤编写的未来任务代码如下所示:

		// 根据代码段实例创建一个未来任务
		futuretask<integer> future = new futuretask<integer>(callable);
		future.run(); // 运行未来任务
		try {
			integer result = future.get(); // 获取未来任务的执行结果
			printutils.print(thread.currentthread().getname(), "主线程的执行结果="+result);
		} catch (interruptedexception | executionexception e) {
			// get方法会一直等到未来任务的执行完成,由于等待期间可能收到中断信号,因此这里得捕捉中断异常
			e.printstacktrace();
		}

 

运行上述的任务调用代码,观察到的任务日志如下:

16:48:53.363 main 任务生成的随机数=11
16:48:53.422 main 主线程的执行结果=11

 

有没有发现什么不对劲的地方?第一行日志是在callable实例的call方法中打印的,第二行日志是在主线程获得返回值后打印的,可是从日志看,两行日志都由main线程也就是主线程输出的,说明未来任务仍然由主线程执行,而非由分线程执行。
那么如何才能开启分线程来执行未来任务呢?当然还得让thread类亲自出马了,就像使用分线程执行runnable任务那样,同样要把callable实例放入thread的构造方法当中,然后再调用线程实例的start方法方可启动线程任务。于是添加线程类之后的未来任务代码变成了下面这样:

		// 根据代码段实例创建一个未来任务
		futuretask<integer> future = new futuretask<integer>(callable);
		// 把未来任务放入新创建的线程中,并启动分线程处理
		new thread(future).start();
		try {
			integer result = future.get(); // 获取未来任务的执行结果
			printutils.print(thread.currentthread().getname(), "主线程的执行结果="+result);
		} catch (interruptedexception | executionexception e) {
			// get方法会一直等到未来任务的执行完成,由于等待期间可能收到中断信号,因此这里得捕捉中断异常
			e.printstacktrace();
		}

 

运行上面的未来任务代码,观察到以下的程序日志:

16:49:49.816 thread-0 任务生成的随机数=38
16:49:49.820 main 主线程的执行结果=38

 

从日志中的thread-0名称可知,此时的未来任务总算交由分线程执行了。



更多java技术文章参见《java开发笔记(序)章节目录