1. 简介:Runnable和Thread
为什么要有,Callable接口和Runnable接口?
我们知道创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread,但是这两种创建线程的方式都有个问题,执行完任务后无法获取返回结果,于是,就有了Callable接口和Runnable接口。
看看创建线程的两个方法:
Class MyThread extends Thread{
@Override
Void run(){
}
}
new MyThread().start();
Class MyTask implements Runnable {
Void run(){}
}
new Thread( new MyTask()).start();
顺便说下:
- new MyThread().start():会启动一个线程执行run方法
- new MyThread().run();:在本线程中执行run方法
那么Runnable和Thread有什么区别呢其实这二者本身就没有本质区别,就是接口和类的区别!!
- 创建线程的方式有两种,一种是实现Runnable接口,另一种是继承Thread。
- Runnable接口支持多继承,但基本上用不到,就是接口的特性而已。
- Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。
目前网络上其实流传着一个谬论:Runnable更容易可以实现多个线程间的资源共享,而Thread不可以!
网上的例子:
program–Thread:
//program--Thread
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
new MyThread().start();
new MyThread().start();
}
static class MyThread extends Thread{
private int ticket = 5;
public void run(){
while(true){
System.out.println("Thread ticket = " + ticket--);
if(ticket < 0){
break;
}
}
}
}
}
//结果:
Thread ticket = 5
Thread ticket = 5
Thread ticket = 4
Thread ticket = 3
Thread ticket = 2
Thread ticket = 1
Thread ticket = 0
Thread ticket = 4
Thread ticket = 3
Thread ticket = 2
Thread ticket = 1
Thread ticket = 0
Process finished with exit code 0
program–Runnable:
//program--Runnable
public class Test2 {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread2 mt=new MyThread2();
new Thread(mt).start();
new Thread(mt).start();
}
static class MyThread2 implements Runnable{
private int ticket = 5;
public void run(){
while(true){
System.out.println("Runnable ticket = " + ticket--);
if(ticket < 0){
break;
}
}
}
}
}
//结果:
Runnable ticket = 5
Runnable ticket = 4
Runnable ticket = 3
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 2
Process finished with exit code 0
然后就会非常郑重的得出:Runnable更容易可以实现多个线程间的资源共享,而Thread不可以!
program–Thread这个例子结果多卖一倍票的原因根本不是因为Runnable和Thread的区别,看其中的如下两行代码:
new MyThread().start();
new MyThread().start();
创建了两个MyThread对象,每个对象都有自己的ticket成员变量,当然会多卖1倍。如果把ticket定义为static类型,就离正确结果有近了一步(因为是多线程同时访问一个变量会有同步问题,加上锁才是最终正确的代码)。
现在看program–Runnable例子中,如下代码:
MyThread2 mt=new MyThread2();
new Thread(mt).start();
new Thread(mt).start();
只创建了一个Runnable对象,肯定只卖一倍票(但也会有多线程同步问题,同样需要加锁),根本不是Runnable和Thread的区别造成的。再来看一个使用Thread方式的正确例子:
public class Test3 extends Thread {
private int ticket = 10;
public void run(){
for(int i =0;i<10;i++){
synchronized (this){
if(this.ticket>0){
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"卖票---->"+(this.ticket--));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] arg){
Test3 t1 = new Test3();
new Thread(t1,"线程1").start();
new Thread(t1,"线程2").start();
}
}
//结果:
线程1卖票---->10
线程1卖票---->9
线程1卖票---->8
线程1卖票---->7
线程1卖票---->6
线程1卖票---->5
线程1卖票---->4
线程1卖票---->3
线程1卖票---->2
线程1卖票---->1
Process finished with exit code 0
上例中只创建了一个Thread对象(子类Test3),效果和Runnable一样。synchronized这个关键字是必须的,否则会出现同步问题。
Thread和Runnable的实质是继承关系,没有可比性。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable。
2. 具体看看:callable接口 / Runnable接口 / Future接口
Callbale,Future,FutureTask这三个方法只能用于线程池中,Runnable则可以同时在运用在Thread和线程池中。
Callable:
Callable接口和Runnable接口一样,也可通过实现该接口启动线程,但是Callable接口的run方法有返回值。
public interface Callable<V>{
//返回V类型的结果
V call() throws Exception;
}
//为加深理解,假如Runnable的源码作为对比
public interface Runnable{
//返回V类型的结果
public abstract void run();
}
该接口声明了一个名称为 call() 的方法,同时这个方法可以有返回值 V,也可以抛出异常。
Future接口 ,相当于是对Callable进行了封装:
- Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
- 提供了对Runnable或者Callable的任务的执行结果进行管理
- cancel:取消,
- isDone:查询是否完成,
- get:获取结果,get函数调用后会发生阻塞,直到执行完成返回结果
- set:设置结果;
public interface Future<V> {
// 如果任务还没有开始,执行 cancel() 方法将返回false,如果任务已经启动,执行 cancel(true) 方法将
// 已中断执行此任务线程的方式来试图停止任务,如果停止成功,返回 true,当任务已经启动,执行
// cancel(false) 方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回 false
// 当任务已经完成,执行 cancel() 方法将返回 false ,mayInterruptlfRunning 参数表示是否中断执行中的
// 线程,通过方法分析我们也知道实际上 Future 提供了 3 中可能:1.能够中断执行中的任务,2.判断
// 任务是否执行完成,3.获取任务执行完成后的结果,但是 Future 是一个接口,我们无法直接创建对象
// 因此就需要其实现类 FutureTask
boolean cancel(boolean mayInterruptIfRunning);
// 如果任务完成前被取消,则返回 true
boolean isCancelled();
// 如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回 true
boolean isDone();
// 该方法用于获取异步执行的结果,如果没有结果返回,该方法会一直阻塞直到任务执行完成
V get() throws InterruptedException, ExecutionException;
// 获取异步执行结果,如果没有结果返回,此方法会一直阻塞,但是会有时间的限制
// 如果阻塞时间超过设定的 timeout 时间,该方法会抛出异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask
FutureTask是Future的一个实现,使用实例:
FutureTask实现了RunnableFuture,而RunnableFuture实现了Runnable又实现了Future这两个接口,因此FutureTask同时具有两者的能力。
public class FutureTask<V> implements RunnableFuture<V>{
//代码省略
}
public class RunnabelFuture<V> implements Runnable,Future<V>{
void run();
}
FutureTask futureTask = new FutureTask(new Callable(){});
new Thread(futureTask).start();
MyResult r = futureTask.get();
-
执行start方法后会执行Callable的run方法。调用futureTask 的get方法会执行,并判断是否执行完,如果run执行完了直接返回结果,否则的话会awaitDone阻塞,
-
当start方法中的Callable方法执行完后会判断有没有阻塞的线程有的话会唤醒(LockSupport.parkNanos阻塞,LockSupport.unpark(thread)唤醒)
-
注意当有多个线程阻塞的时候,是以链表形式存储的,唤醒的时候也是依次唤醒
无论是 Runnable 接口的实现类还是 Callable 接口的实现类,都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行
ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了ExcutorService 接口,而因此 Callable 需要和Executor 框架中的 ExcutorService 结合使用,我们先看看 ExecutorService 提供的方法:
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
}
public <T> Future<T> submit(Runnable task, T result) {
return schedule(Executors.callable(task, result), 0, NANOSECONDS);
}
public <T> Future<T> submit(Callable<T> task) {
return schedule(task, 0, NANOSECONDS);
}
- 第一个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。
- 第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
- 第三个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可
还有点要注意的是,除了我们自己实现Callable对象外,我们还可以使用工厂类Executors来把一个Runnable对象包装成Callable对象。Executors工厂类提供的方法如下:
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
简单演示Runnable,Callable,FutureTask的运用:
public class FutureDemo {
static ExecutorService mExecutor=Executors.newSingleThreadExecutor();
public static void main(String[] args){
try {
FutureWithRunnable();
FutureWithCallable();
FutureWithFutureTask();
} catch (Exception e) {
// TODO: handle exception
}
}
private static void FutureWithRunnable() throws InterruptedException,ExecutionException{
Future<?> result=mExecutor.submit(new Runnable() {
public void run() {
// TODO Auto-generated method stub
fibc(20);
}
});
System.out.println("Future result from Runnable : "+result.get());
}
private static void FutureWithCallable() throws InterruptedException,ExecutionException{
Future<Integer> result2=mExecutor.submit(new Callable<Integer>(){
public Integer call() throws Exception {
// TODO Auto-generated method stub
return fibc(20);
}
});
System.out.println("Future result from Callable : "+result2.get());
}
private static void FutureWithFutureTask() throws InterruptedException,ExecutionException{
FutureTask<Integer> futuretask=new FutureTask<Integer>(
new Callable<Integer>() {
public Integer call() throws Exception {
return fibc(20);
}
});
mExecutor.submit(futuretask);
System.out.println("Future result from FutureTask : "+futuretask.get());
}
private static int fibc(int num){
if(num == 0)
return 0;
if(num == 1)
return 1;
return fibc(num-1)+fibc(num-2);
}
}
3. 举个例子
在互联网应用中有时候可能数据量会很大会把数据存储在不同的服务器,查询的时候会去查询不通的服务器,然后在把数据汇总,传统查询查完 A 服务器 再去 查询 B 服务然后在汇总,这样可能会导致等带的时间会比较长,使用 future 带返回值得线程就可以很好的解决这样的问题,在查询 A 服务器的同时去查询 B 服务器,最后在把数据汇总,例如
public class ExecutorFutureDemo implements Callable<Integer> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1,
1,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
Future<Integer> result = executor.submit(new ExecutorFutureDemo());
System.out.println("开始查询服务器 B ...");
// 保存服务器 B 查询结果
int sum = 0;
// 主线程查询服务器 B 的结果
for (int i = 1; i <= 100 ; i++) {
sum += i;
}
// 模拟服务器 B 的查询时间
Thread.sleep(3000);
System.out.println("服务器 B 查询完毕。");
if(!result.isDone()){
System.out.println("服务器 A 数数据还没有查询完毕...");
}
// 在没有得到结果之前会一直阻塞在这里,直到拿到返回结果
Integer aSum = result.get();
long endTime = System.currentTimeMillis();
System.out.println("查询结果="+(aSum+sum)+",总耗时="+(endTime-startTime)/1000);
// 关闭线程池
executor.shutdown();
}
@Override
public Integer call() throws Exception {
System.out.println("开始查询服务器 A ...");
// 保存服务器 A 查询结果
int sum = 0;
try {
// 模拟服务器 A 的 查询时间
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i <= 50 ; i++) {
sum += i;
}
System.out.println("服务器 A 查询完毕。");
return sum;
}
}
开始查询服务器 B ...
开始查询服务器 A ...
服务器 B 查询完毕。
服务器 A 数数据还没有查询完毕...
服务器 A 查询完毕。
查询结果=6325,总耗时= 5 秒