• 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

    您的资助是我最大的动力!
    金额随意,欢迎来赏!
    款后有任何问题请给我留言。

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我。(●'◡'●)

    如果你觉得本篇文章对你有所帮助,请给予我更多的鼓励,求打             付款后有任何问题请给我留言!!!

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【Jack_孟】!

  • 相关阅读:
    C#的多态性
    冒泡法,改进冒泡法排序和反序算法
    [收藏]D3D渲染流程简介
    [收藏]字符串表达式求值
    Windows下使用VC++编译GNU科学计算库 GNU Scientific Library(GSL)
    VC++ 高精度定时timeSetEvent和回调成员方法
    [收藏]C/C++数组名与指针区别深层探索
    线性表操作
    线性表综合运用
    两个线性表融合的算法
  • 原文地址:https://www.cnblogs.com/mq0036/p/14475488.html
Copyright © 2020-2023  润新知