Java多线程Thread/Runnable/Callable之间的区别

编写多线程程序一般有三种方法,Thread,Runnable,Callable。

1. Runable

Runnable是个接口,使用很简单:

  • 1. 实现该接口并重写run方法
  • 2. 利用该类的对象创建线程
  • 3. 线程启动时就会自动调用该对象的run方法
package com.callable.runnable;
/**
* Created on 2016/5/18.
*/
public class RunnableImpl implements Runnable {
public RunnableImpl(String acceptStr) {
this.acceptStr = acceptStr;
}
private String acceptStr;
@Override
public void run() {
try {
// 线程阻塞 1 秒,此时有异常产生,只能在方法内部消化,无法上抛
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 最终处理结果无法返回
System.out.println("hello : " + this.acceptStr);
}
public static void main(String[] args) {
Runnable runnable = new RunnableImpl("my runable test!");
long beginTime = System.currentTimeMillis();
new Thread(runnable).start();
long endTime = System.currentTimeMillis();
System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
}
}

Runnable实现的是void run()方法,Callable实现的是 V call()方法,并且可以返回执行结果,其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。通常在开发中结合ExecutorService使用,将任务的提交与任务的执行解耦开,同时也能更好地利用Executor提供的各种特性

2. Callable

相对于继承Thread来创建线程方式,使用Runnable可以让你的实现类同时实现多个接口,而相对于Callable及Future,Runnable方法并不返回任务执行结果且不能抛出异常。

Callable的接口定义如下:

public interface Callable<V> {
V call() throws Exception;
}

Callable并不像Runnable那样通过Thread的start方法就能启动实现类的run方法,所以它通常利用ExecutorService的submit方法去启动call方法自执行任务,而ExecutorService的submit又返回一个Future类型的结果,因此Callable通常也与Future一起使用

 

ExecutorService pool = Executors.newCachedThreadPool();
Future<String> future = pool.submit(new Callable{
public void call(){
//TODO
}
});

步骤1:创建实现Callable接口的类SomeCallable<Integer>(略);

 

步骤2:创建一个类对象:
Callable<Integer> oneCallable = new SomeCallable<Integer>();
步骤3:由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<Integer> oneTask = new FutureTask<Integer>(oneCallable);

注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。

 

步骤4:由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = new Thread(oneTask);

步骤5:启动线程:

oneThread.start();

 

此处也可以使用Callable和Fiture实现调用:

 

public class CallableTest {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
CallableDemo calTask=new CallableDemo();
//提交任务并获取执行结果
Future<Integer> future =es.submit(calTask);
//关闭线程池
es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if(future.get()!=null){
//输出获取到的结果
System.out.println("future.get()-->"+future.get());
}else{
//输出获取到的结果
System.out.println("future.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}

 

此处也可以使用Callable+FutureTask的方式实现调用:

 

public class CallableTest {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
CallableDemo calTask=new CallableDemo();
//创建FutureTask
FutureTask<Integer> futureTask=new FutureTask<>(calTask);
//执行任务
es.submit(futureTask);
//关闭线程池
es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if(futureTask.get()!=null){
//输出获取到的结果
System.out.println("futureTask.get()-->"+futureTask.get());
}else{
//输出获取到的结果
System.out.println("futureTask.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}

3. Thread

通过继承Thread类来创建一个线程:
步骤1:定义一个继承Thread类的子类:

 

class SomeThead extends Thraad
{
public void run()
{
//do something here
}
}

 

步骤2:构造子类的一个对象:

SomeThread oneThread = new SomeThread();
步骤3:启动线程:
oneThread.start();
完整版例程如下:

 

package com.clzhang.sample.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 实现Callable接口来实现线程
public class ThreadByCallable implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("当前线程名称是:" + Thread.currentThread().getName());
int i = 0;
for (; i < 5; i++) {
System.out.println("循环变量i的值:" + i);
}
// call()方法有返回值
return i;
}
public static void main(String[] args) {
ThreadByCallable rt = new ThreadByCallable();
// 使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<Integer>(rt);
new Thread(task, "有返回值的线程").start();
try {
// 获取线程返回值
System.out.println("子线程的返回值:" + task.get());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}


至此,一个线程就创建完成了。

 

4. 线程池方法

步骤1:创建线程池:

ExecutorService pool = Executors.newCachedThreadPool();
步骤2:通过Runnable对象或Callable对象将任务提交给ExecutorService对象:
Future<Integer> submit(Callable<Integer> task);
注释:Future是一个接口,它的定义如下:
public interface Future<T>
{
V get() throws ...;
V get(long timeout, TimeUnit unit) throws ...;
void cancle(boolean mayInterrupt);
boolean isCancelled();
boolean isDone();
}
至此,一个线程就创建完成了。
注释:线程池需调用shutdown();方法来关闭线程。

 

详细例程如下:

 

public class CallableTest {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
CallableDemo calTask=new CallableDemo();
//提交任务并获取执行结果
Future<Integer> future =es.submit(calTask);
//关闭线程池
es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if(future.get()!=null){
//输出获取到的结果
System.out.println("future.get()-->"+future.get());
}else{
//输出获取到的结果
System.out.println("future.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}

5. Runnable和Callable的区别是:

(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得。
(3)call方法可以抛出异常,run方法不可以。
(4)运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会跑出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future<?> 形式类型、并返回 null 作为底层任务的结果。

 

6. Future

Future保存异步计算的结果,可以在我们执行任务时去做其他工作,并提供了以下几个方法
* cancel(boolean mayInterruptIfRunning):试图取消执行的任务,参数为true时直接中断正在执行的任务,否则直到当前任务执行完成,成功取消后返回true,否则返回false
* isCancel():判断任务是否在正常执行完前被取消的,如果是则返回true
* isDone():判断任务是否已完成
* get():等待计算结果的返回,如果计算被取消了则抛出
* get(long timeout,TimeUtil unit):设定计算结果的返回时间,如果在规定时间内没有返回计算结果则抛出TimeOutException
使用Future的好处:
1. 获取任务的结果,判断任务是否完成,中断任务
1. Future的get方法很好的替代的了Thread.join或Thread,join(long millis)
2. Future的get方法可以判断程序代码(任务)的执行是否超时,如:
try{
future.get(60,TimeUtil.SECOND);
}catch(TimeoutException timeout){
log4j.log("任务越野,将被取消!!");
future.cancel();
}

 

7. FutureTask

FutureTask实现了RunnableFuture接口,提供了即可以使用Runnable来执行任务,又可以使用Future执行任务并取得结果的构造器,所以可以利用FutureTask去封装Runnable或Callable对象,之后再submit任务。
public class FutureTask<V> implements RunnableFuture<V> {
...
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。 当然也可以调用线程直接执行(FutureTask.run())。接下来我们根据FutureTask.run()的执行时机来分析其所处的3种状态:
(1)未启动,FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态,当创建一个FutureTask,而且没有执行FutureTask.run()方法前,这个FutureTask也处于 未启动状态
(2)已启动,FutureTask.run()被执行的过程中,FutureTask处于 已启动状态
(3)已完成,FutureTask.run()方法执行完正常结束,或者被取消或者抛出异常而结束,FutureTask都处于 完成状态.

8. Callable和Runable的转换

无论是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行,ThreadPoolExecutor或ScheduledThreadPoolExecutor都实现了ExcutorService接口,而因此Callable需要和Executor框架中的ExcutorService结合使用,我们先看看ExecutorService提供的方法:
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
第一个方法:submit提交一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
第二个方法:submit提交一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
第三个方法:submit提交一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。
因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。还有点要注意的是,除了我们自己实现Callable对象外,我们还可以使用工厂类Executors来把一个Runnable对象包装成Callable对象。Executors工厂类提供的方法如下:
public static Callable<Object> callable(Runnable task)
public static <T> Callable<T> callable(Runnable task, T result)

9. 多线程方式调用

利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。
package futuretask;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class FutureTaskForMultiCompute {
public static void main(String[] args) {
FutureTaskForMultiCompute inst=new FutureTaskForMultiCompute();
// 创建任务集合
List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
// 创建线程池
ExecutorService exec = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
// 传入Callable对象创建FutureTask对象
FutureTask<Integer> ft = new FutureTask<Integer>(inst.new ComputeTask(i, ""+i));
taskList.add(ft);
// 提交给线程池执行任务,也可以通过exec.invokeAll(taskList)一次性提交所有任务;
exec.submit(ft);
}
System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");
// 开始统计各计算线程计算结果
Integer totalResult = 0;
for (FutureTask<Integer> ft : taskList) {
try {
//FutureTask的get方法会自动阻塞,直到获取计算结果为止
totalResult = totalResult + ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 关闭线程池
exec.shutdown();
System.out.println("多任务计算后的总结果是:" + totalResult);
}
private class ComputeTask implements Callable<Integer> {
private Integer result = 0;
private String taskName = "";
public ComputeTask(Integer iniResult, String taskName){
result = iniResult;
this.taskName = taskName;
System.out.println("生成子线程计算任务: "+taskName);
}
public String getTaskName(){
return this.taskName;
}
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
result =+ i;
}
// 休眠5秒钟,观察主线程行为,预期的结果是主线程会继续执行,到要取得FutureTask的结果是等待直至完成。
Thread.sleep(5000);
System.out.println("子线程计算任务: "+taskName+" 执行完成!");
return result;
}
}
}
10. Thread Join方法
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
t.join(); //使调用线程 t 在此之前执行完毕。
t.join(1000); //等待 t 线程,等待时间是1000毫秒

如下代码:
public class JoinTest implements Runnable{
public static int a = 0;
public void run() {
for (int k = 0; k < 5; k++) {
a = a + 1;
}
}
public static void main(String[] args) throws Exception {
Runnable r = new JoinTest();
Thread t = new Thread(r);
t.start();
System.out.println(a);
}
}
程序的输出结果是5吗?答案是:有可能。其实你很难遇到输出5的时候,通常情况下 都不是5。当然这也和机器有严重的关系。为什么呢?我的解释是当主线程 main方法执行System.out.println(a);这条语句时, 线程还没有真正开始运行,或许正在为它分配资源准备运行。因为为线程分配资源需要时间,而main方法执行完t.start()方法后继续往下执行System.out.println(a);,这个时候得到的结果是a还没有被 改变的值0 。怎样才能让输出结果为5!其实很简单,join() 方法提供了这种功能。join() 方法,它能够使调用该方法的线程在此之前执行完毕。
public static void main(String[] args) throws Exception {
Runnable r = new JoinTest();
Thread t = new Thread(r);
t.start();
t.join(); //加入join()
System.out.println(a);
}
这个时候,程序输入结果始终为5。
 

 

出处:https://blog.csdn.net/KingCat666/article/details/77651024

posted on 2021-03-03 16:54  jack_Meng  阅读(451)  评论(0编辑  收藏  举报

导航