Callable带有返回值的任务,打断线程执行的随机性

232 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

回顾上文Java并发基础(一) - 掘金 (juejin.cn),我们知道

  1. 通过实现Runnable接口可以定义一个任务;
  2. Main方法中直接调用任务的run()方法是使用Main函数的主线程调用的任务
  3. 通过Thread类生成的对象传入任务,调用对象的start()方法会为该线程执行必需的初始化操作,并且调用Runnable的run()方法。
  4. 然后又了解了Executor可以帮助我们更好的去管理线程,因为它有线程池的概念。
  5. 对了,我们还补充了不用理会线程组的概念,因为它是不被推荐的。

本文内容

  1. 定义任务的第二种方式Callable,它和Runnable的区别是,它可以在任务完成时返回一个值。
  2. 了解影响任务执行的行为:休眠、让步、优先级

带返回值的任务

我们对照着Runnable来学,首先我们需要定义这个Callable任务。

static class CallableDemo implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("hi");
        return "芜湖起飞";
    }
}

Callable<String>通过泛型定义,表示它返回值是String类型。

在主线程中直接生成对象调用,使用call()方法执行,会执行return之前的内容,返回值可以通过变量接收。

public static void main(String[] args) throws Exception {
    CallableDemo callableDemo = new CallableDemo();
    String call = callableDemo.call();
    System.out.println(call);
}

和之前一样,这里是使用Main方法的线程,如果说要另起一个新的线程呢?

Thread thread = new Thread(new CallableDemo());

这样尝试会报错,因为Thread只能传入实现Runnable接口的对象。我们会再想到使用Executors去处理。

submit()

sunmit()提交任务以供执行,并返回一个Future,表示该任务的结果。通过调用Future的get方法将在成功完成任务时返回任务的结果。

具体步骤描述

使用Executors构建一个线程池对象,调用其submit()方法,它支持传入一个Callable对象。返回值是一个Future对象。

image.png

Future对象

Future表示异步计算的结果。提供了用于检查计算是否完成、等待计算完成以及检索计算结果的方法。

  1. isDone():查询Future是否已经完成,如果完成可以调用get()获取结果。
  2. get():获取结果,如果没有结果,线程将会被阻塞,直到结果出来。

看看代码具体步骤:

ExecutorService exec = Executors.newCachedThreadPool(); // 声明一个线程池
Future<String> result = exec.submit(new CallableDemo()); // 调用submit方法,得到一个Futre对象
String s = result.get();  // 直接拿结果
System.out.println(s); // 输出结果
exec.shutdown(); //记得关闭线程池

影响任务行为

线程的执行是因为CPU在该时间片段下执行的是你这个任务,如果没有做线程优先级的话,分配规则是随机的,如果要打乱这种随机性,我们在定义任务本身时,就可以在编写业务逻辑时,对任务所做的事情做一些设置,例如是否要让其他线程去执行。

休眠

注意,该线程休眠后,下一个被执行的线程是随机的。(如果没有特殊设置,理论上是这样)

休眠是需要指定一个时间值,在该时间值中任务将会被终止,时间结束后会恢复执行。在JavaSE5/6中,推荐使用TimeUnit.xx.sleep()的方式去替换Thread.sleep(),因为会有更好的可读性。

使用

TimeUnit.xx.sleep()

在任务中,需要终止的地方加上该命令即可。因为需要设定时间,我们关注一下有哪些时间可以选择:

  1. TimeUnit.SECONDS:设置秒级,几秒
  2. TimeUnit.MILLISECONDS:设置毫秒级,几毫秒;1秒=1000毫秒
  3. TimeUnit.DAYS:设置天数,几天
  4. TimeUnit.HOURS:设置小时,几小时
  5. TimeUnit.MINUTES:设置分钟,几分钟

让步

让步的意思是,你知道在一个定义的任务中,编写的业务代码执行的差不多了,这时候需要空出CPU的时间片让给其他线程去做事情了,可以使用yield()发出一个“建议”,表示其他具有相同优先级的线程可以运行。

使用

在任务中,添加:Thread.yield()即可。

区别休眠和让步,休眠很强制性,在该时间段内该任务就是不能执行!优先级较低的任务有可能会被执行。让步的话可能让出去了,但是下一秒还是线程本身,优先级较低的线程还是无法占用CPU。

优先级

在绝大多数时间里,所有线程都应该以默认的优先级运行。试图操纵线程优先级通常是一种错误。这是在Java编程思想中提及的。所以它用的少吧,简单了解一下。

在任务的业务中,通过调用Thread.currentThread.setPriority(优先级)的方式控制当前线程的优先级。每个线程都有一个优先级,由110之间的整数表示。

可以在任务的属性中添加优先级,在new方法调用时通过setter的方式注入该优先级属性。

Thread类提供3个常量属性:

  1. Thread.MAX_PRIORITY:最高优先,值为10
  2. Thread.NORM_PRIORITY:普通优先,值为5
  3. Thread.MIN_PRIORITY:最低优先,值为1

这个只是改变概率和权重,并非是10就一定比5优先,只是概率上说大概率会比5优先。