Java线程的创建方式三:Callable(四)
一、Java实现多线程的三种方式
方式一:继承Thread类:
public class Test extends Thread {
public static void main(String[] args) {
Thread t = new Test();
t.start();
}
@Override
public void run() {
System.out.println("Override run() ...");
}
}
方式二:实现Runnable接口,并覆写run方法:
public class Test implements Runnable {
public static void main(String[] args) {
Thread t = new Thread(new Test());
t.start();
}
@Override
public void run() {
System.out.println("Override run() ...");
}
}
我们今天来学习另外一种创建多线程的方式:实现Callable接口,并覆call方法
方式三:实现Callable接口,并覆call方法:
//1.创建一个线程类,实现Callable接口,
//2.Callable会报一个黄色的警告,原因:可以加个泛型,这个泛型,是返回值对应的类型。
//3.我们这个题是产生随机数,所以加个泛型Integer
public class RanDomCallable implements Callable<Integer> {
//4.一旦上面的泛型确定了,那么这个重写的方法的返回值类型就是Integer了。
public Integer call() throws Exception {
// 睡眠2秒
Thread.sleep(2000);
return new Random().nextInt(10);
}
//5.写main方法测试
public static void main(String[] args) throws InterruptedException, ExecutionException {
//6.创建一个线程对象:
RanDomCallable rdc=new RanDomCallable();
//7.启动线程,直接用Thread传rdc不行,必须再借助一个FutureTask
FutureTask<Integer> ft=new FutureTask<Integer>(rdc);
Thread t=new Thread(ft);//FutureTask实现了Runnable接口所以可以传入
t.start();
//8.上面已经将线程启动了,直接运行是没有结果的
//9.我们必须要对返回值处理,那么如何接收返回值:
Integer i = ft.get();//get方法中可以加入sleep,验证get是个阻塞方法
//10.判断线程是否执行结束:
System.out.println(ft.isDone());
System.out.println(i);
System.out.println(ft.isDone());
}
}
上面代码的执行结果是:每两秒输出一个随机数,原因就是get方法是阻塞的啊,要执行完才会得到结果的。
我们看看FutureTask的源码:
(1)属性
// 底层线程执行的状态标识,具体的数值就是下面int类型的数量
private volatile int state;
// 正常的状态只有用到0,1,2,程序从0->1->2线程就执行完了
// 线程走完,最终state的数值,从1(new)变为2(completing)变为3(normal) 那么线程就执行好了!
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
/** The underlying callable; nulled out after running */
// 要执行的任务
private Callable<V> callable;
/** The result to return or exception to throw from get() */
// 返回值存储在这里
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
// 执行任务的线程
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
(2)构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
// 初始状态state = 0
this.state = NEW; // ensure visibility of callable
}
(3)run方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
// 初始状态时state == NEW == 0
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 调用call()
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
// 若上面没有异常就会走这个方法
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
(4)set方法
protected void set(V v) {
// 状态从0->1(COMPLETING)
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 返回值放入outcome中
outcome = v;
// 状态从1->2(NORMAL)
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
(5)get方法
public V get() throws InterruptedException, ExecutionException {
int s = state;
// 如果状态是<=1的,那么,就进入awaitDone(死循环)
// 什么时候状态变更为2,什么时候return
// 所以会阻塞在这里,要一直等待状态变为2
if (s <= COMPLETING)
s = awaitDone(false, 0L);
// 调用report方法,传入state = 2
return report(s);
}
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;
// 只有当state == NORMAL,才会将返回值返回
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
二、Callable和Future出现的原因
创建线程的2种方式:
一种是直接继承Thread;
另外一种就是实现Runnable接口。
这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。从Java 1.5开始提供了Callable和Future两个接口,通过使用它们可以在任务执行完毕后得到执行结果。
三、Callable和Future介绍
Callable接口代表一段可以调用并返回结果的代码;
Future接口表示异步任务,是还没有完成的任务给出的未来结果。
所以说Callable用于产生结果,Future用于获取结果。
Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法在线程池中执行Callable内的任务。由于Callable任务是并行的(并行就是整体看上去是并行的,其实在某个时间点只有一个线程在执行),我们必须等待它返回的结果。
java.util.concurrent.Future对象为我们解决了这个问题。在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法(阻塞的方法)让我们可以等待Callable结束并获取它的执行结果。
也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask,FutureTask实现了RunnableFuture接口。FutureTask已经在上面介绍过了,这里就不赘述了。
参考链接
本片文章,主要整理自互联网,便于自己复习知识所用,以下为参考链接!
【1】Java程序员必须掌握的线程知识-Callable和Future
【2】Callable原理,线程池执行Callable任务
【3】Java多线程之Callable接口及线程池
Java线程的创建方式三:Callable(四)的更多相关文章
- 【多线程】线程创建方式三:实现callable接口
线程创建方式三:实现callable接口 代码示例: import org.apache.commons.io.FileUtils; import java.io.File; import java. ...
- Java多线程——线程的创建方式
Java多线程——线程的创建方式 摘要:本文主要学习了线程的创建方式,线程的常用属性和方法,以及线程的几个基本状态. 部分内容来自以下博客: https://www.cnblogs.com/dolph ...
- Java之解决线程安全问题的方式三:Lock锁
import java.util.concurrent.locks.ReentrantLock; /** * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增 * * 1. 面试题:sy ...
- Java线程:创建与启动
Java线程:创建与启动 一.定义线程 1.扩展java.lang.Thread类. 此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的 R ...
- 漫谈并发编程(二):java线程的创建与基本控制
java线程的创建 定义任务 在java中使用任务这个名词来表示一个线程控制流的代码段,用Runnable接口来标记一个任务,该接口的run方法为线程运行的代码段. public ...
- JAVA - 线程从创建到死亡的几种状态都有哪些?
JAVA - 线程从创建到死亡的几种状态都有哪些? 新建( new ):新创建了一个线程对象. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 sta ...
- java线程(1)——三种创建线程的方式
前言 线程,英文Thread.在java中,创建线程的方式有三种: 1.Thread 2.Runnable 3.Callable 在详细介绍下这几种方式之前,我们先来看下Thread类和Runnabl ...
- Java线程实现的第三种方式Callable方式与结合Future获取返回值
多线程的实现方式有实现Runnable接口和继承Thread类(实际上Thread类也实现了Runnable接口),但是Runnable接口的方式有两个弊端,第一个是不能获取返回结果,第二个是不能抛出 ...
- java多线程 -- 创建线程的第三者方式 实现Callable接口
Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个 ...
随机推荐
- 重置 Mac 上的 NVRAM 或 PRAM
https://support.apple.com/zh-cn/HT204063 如果 Mac 出现了与 NVRAM 或 PRAM 中储存的设置有关的问题,那么进行重置可能会有帮助. NVRAM( ...
- IDEA+JUnit
1.入门 https://blog.csdn.net/smxjant/article/details/78206279 2.比较好的JUnit例子:https://github.com/aws/aws ...
- selenium:断言
在编写自动化测试脚本时,为了使“机器”去自动辨识test case的执行结果是True还是False,一般都需要在用例执行过程中获取一些信息,来判断用例的执行时成功还是失败. 判断成功失败与否,就涉及 ...
- UVA - 10931-Parity
题意:1.输入一个数,将其转换为二进制.2.记录二进制中出现1的次数. 注意:转换二进制后直接输出,不能转换为十进制后输出 #include<iostream> #include<c ...
- 【重磅干货整理】机器学习(Machine Learning)与深度学习(Deep Learning)资料汇总
[重磅干货整理]机器学习(Machine Learning)与深度学习(Deep Learning)资料汇总 .
- Elasticsearch 思维导图集锦(持续更新...)
目录 引言 思维导图 全文搜索 Elastic 基础 Query DSL Multi Match Query 系列文章列表 参考 引言 本文主要是对 elasticsearch 的一些知识点使用思维导 ...
- 分享一个公众号h5裂变吸粉源码工具
这次我是分享我本人制作的一个恶搞程序,说白了就是一个公众号裂变吸粉工具,市面上有很多引流方法,例如最常见的就是色流,哈哈,今天我跟大家分享的方法是有趣的,好玩的,恶搞的.这个程序上线一天已经收获了61 ...
- 二维数组中的查找问题--剑指offer面试题3
题目:在一个二维数组中,对每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. // 二维数组中的查找 ...
- Python-collections模块-57
返回顶部 模块的导入和使用 模块的导入应该在程序开始的地方 更多相关内容 http://www.cnblogs.com/Eva-J/articles/7292109.html 常用模块 colle ...
- python中map()函数用法
map函数的原型是map(function, iterable, …),它的返回结果是一个列表. 参数function传的是一个函数名,可以是python内置的,也可以是自定义的. 参数iterabl ...