程序猿学社的GitHub,欢迎Starhttps://github.com/ITfqyd/cxyxs
本文已记录到github,形成对应专题。

前言

通过上一章实现多线程有几种方式,我们已经了解多线程实现的方式。但是,一般大家知道的就两种,继承Thread类和实现runnable接口。对Callable接口实现多线程很陌生,我们看通过源码学习,了解callable是怎么一回事。

1.Callable的前世今生

通过runnable接口或者继承Thread实现一个多线程,我们想要获取线程的运行结果,可以通过共享变量的方式来获取。为了解决这个问题,java在jdk1.5中引入callable、Future接口。

1.1 Callable的前世Runnable

package com.cxyxs.thread.three;import java.util.Date;/*** Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/* Author: 程序猿学社* Date:  2020/2/17 21:43* Modified By:*/
public class MyThreadRunnable implements Runnable{//测试账号private int count=0;@Overridepublic void run() {for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);count++;} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {MyThreadRunnable run = new MyThreadRunnable();Thread thread = new Thread(run);thread.start();try {//等待线程执行完毕,不然获取不到最终的结果thread.join();System.out.println(run.count);} catch (InterruptedException e) {e.printStackTrace();}}
}

测试结果

  • 通过这种方式,我们是不是实现了,获取线程的结果。现在我们总结一下通过runnable方式,是如何实现获取多线程的返回接口的。
  • 定义一个类实现Runnable接口 定义一个成员变量,用来接收run方法运行后,我们需要的结果。
  • 调用时,实例化该类,作为形参传入Thread中,调用start方法启动线程
  • 调用线程的join方法 ,等待线程执行完毕,这样我们才能取到线程执行完后的结果5,不然就是0.

1.2 Callable的今生

通过FutureTask+Thread调用

package com.cxyxs.thread.three;import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;/*** Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/* Author: 程序猿学社* Date:  2020/2/19 0:12* Modified By:*/
public class MyThreadCallable {public static void main(String[] args) throws Exception{//第一步Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int count=0;for (int i = 0; i < 5; i++) {Thread.sleep(1200);count++;}return count;}};FutureTask<Integer> task = new FutureTask<>(callable);Thread thread = new Thread(task);thread.start();Integer result = task.get();System.out.println("获取多线程的值:"+result);}
}

  • 为了偷懒,这里第一步简写了,一般都是类实现Callable接口,重写call,我这里使用简写的方法实现。
  • 实例化一个FutureTask,把实现Callable的类,作为构造方法的形参传入进去。
  • 实例化一个线程,把FutureTask这个对象,作为构造方法的形参传入。
  • 调用FutureTask对象的get方法。就可以获取值。

ExecutorService+Future调用实现

 public void two() throws Exception{Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int count=0;for (int i = 0; i < 5; i++) {Thread.sleep(1200);count++;}return count;}};ExecutorService e= Executors.newFixedThreadPool(10);Future<Integer> f1=e.submit(callable);Integer result = f1.get();System.out.println("获取多线程的值:"+result);}

建议,使用这种方式,通过线程池管理多线程。引出一个问题,为什么要使用线程池?
为了减少创建和销毁线程的次数,让每个线程可以多次使用。
这里了解即可。知道有这么一回事。后续会有相关的文章进行线程池的相关介绍。

2.源码分析

话不多说,就按照我们刚刚总结的通过Callable如何实现一个多线程的步骤。我们对ExecutorService+Future调用实现的方式进行分析,下面我们开始一步步得到查看他的源码。有闲情的社友,可以自己试试看看FutureTask+Thread这种方式调用的源码。

2.1 第一步,实现Callable接口

Callable接口

@FunctionalInterface
public interface Callable<V> {/*** Computes a result, or throws an exception if unable to do so.** @return computed result* @throws Exception if unable to compute a result*/V call() throws Exception;
}

Runnable接口

@FunctionalInterface
public interface Runnable {public abstract void run();
}

看了源码后,各位社友,能说一说Callable和Runnable接口的区别吗?

  • 方法名不同,Callable是call,Runnable是run
  • Callable是方法,Runnable是抽象方法
  • Callable抛出异常,Runnable没有抛出异常
  • Callable有返回值V,Runnable没有返回值。
  • Callable可以定义泛型,这样我们通过get调用时,无需拆箱装箱。
    而抛出异常和有返回值,就是Callable很重要的一个优点,也就是为了解决Runnable一些痛点问题。

2.2 FutureTask类结构图

首先我们先看看futureTask的类结构。在idea中右键,选择digrams->show digrams(idea版本

通过这个图,我们能很好的看出对应的关系,C表示是类,I是接口

@FunctionalInterface是函数式接口。

我们根据这个图,一个个进行源码分析。

通过图,我们可以发现FutureTask实现了RunnableFuture接口,让我们一探究竟。

2.3 RunableFuture接口

public interface RunnableFuture<V> extends Runnable, Future<V> {/*** Sets this Future to the result of its computation* unless it has been cancelled.*/void run();
}

看到这里,我们也就知道我们之前为什么说Callable是Runnable的plus版本。实际上就是在就是在Runnable的基础上做了一些优化。

2.4 Runnable接口

@FunctionalInterface
public interface Runnable {public abstract void run();
}

这个因为之前有阐述过,这里就不过多的描述。

2.5 Future接口

通过查看类或者接口下的所有方法(idea中使用alt+7)

  • cancel方法:还没计算完,可以取消计算过程,
  • isCancelled方法:是否取消
  • isDone方法:是否完成
  • get方法:获取计算结果(如果还没计算完,也是必须等待的)
  • get(long,TimeUnit) 获取计算结果,可以指定一个超时时间,如果在规定时间内,没有返回结果,会抛出一个TimeOutException的异常

2.6 FutureTask源码分析

通过代码,我们发现我们调用FutureTask的构造方法,把callable的实例对象,作为形参,传入进去咯。

   public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW;       // ensure visibility of callable}

如果没有传Callable就抛出NullPointerException.我们发现这里有一个变量state的值为NEW

/** Possible state transitions:* NEW -> COMPLETING -> NORMAL* NEW -> COMPLETING -> EXCEPTIONAL* NEW -> CANCELLED* NEW -> INTERRUPTING -> INTERRUPTED*/private volatile int state;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;

  • NEW(初始化)
  • COMPLETING(运行中)
  • NORMAL(完成状态)
  • EXCEPTIONAL(任务因异常而结束)
  • CANCELLED(还未运行就调用cancel(true)方法取消)
  • INTERRUPTING(正在运行中就调用cancel(true)方法被取消)
  • INTERRUPTED(被取消状态,由INTERRUPTING转换成INTERRUPTED)
    源码注释部分,就是任务的几种状态变化。

一般都是直接把FutureTask对象,作为参数传给Thread类的构造方法传入。

正常情况下NEW -> COMPLETING -> NORMAL

调用new FutureTask把状态改为New

调用FutureTask的run方法改为COMPLETING

中途不发现异常,执行完run方法,就会返回NORMAL

  • 调用task.get可以获取线程执行的结果
 public V get() throws InterruptedException, ExecutionException {int s = state;if (s <= COMPLETING)s = awaitDone(false, 0L);return report(s);}

COMPLETING标识运行中,判断这个任务是否已经在运行中,也就是已调用run方法。说明任务还未完成,我们需要等待,所以这里调用了awaitDone方法。

  • 返回结果是调用的report方法
private V report(int s) throws ExecutionException {Object x = outcome;if (s == NORMAL)return (V)x;if (s >= CANCELLED)throw new CancellationException();throw new ExecutionException((Throwable)x);}

如果状态为已完成(NORMAL),直接返回运行结果。

关注 【 @程序猿学社 】回复关键字,查看更多干货。

libevent c++高并发网络编程_【多线程高并发编程】Callable源码分析相关推荐

  1. Netty网络框架学习笔记-16(心跳(heartbeat)服务源码分析)

    Netty网络框架学习笔记-16(心跳(heartbeat)服务源码分析_2020.06.25) 前言: Netty 作为一个网络框架,提供了诸多功能,比如编码解码等,Netty 还提供了非常重要的一 ...

  2. Java并发基础:了解无锁CAS就从源码分析

    CAS的全称为Compare And Swap,直译就是比较交换.是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在i ...

  3. Java并发基础:了解无锁CAS就从源码分析 1

    CAS的全称为Compare And Swap,直译就是比较交换.是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,在i ...

  4. mvc设计模式现在过时了吗_尚学堂115——设计模式、源码分析以及SpringData

    设计模式 什么是设计模式?你是否在你的代码里面使用过任何设计模式? 设计模式是在软件设计中常见问题的通用.可反复使用.多数人知晓的一种解决方案或模板:这些解决方案是在相当长的一段时间内由众多软件开发人 ...

  5. 死磕java并发cas_死磕 java并发包之AtomicInteger源码分析

    问题 (1)什么是原子操作? (2)原子操作和数据库的ACID有啥关系? (3)AtomicInteger是怎么实现原子操作的? (4)AtomicInteger是有什么缺点? 简介 AtomicIn ...

  6. Android SQLite多线程读写和线程同步源码分析

    没啥诀窍,只需保证几个线程都是用的一个SQLiteDataBase对象就行了. 如果我们非要在不同线程中用两个或更多的SQLiteDataBase对象呢,当然这些SQLiteDataBase对象所操作 ...

  7. windows nginx c++读取请求数据_震撼!全网第一张源码分析全景图揭秘Nginx

    不管是C/C++技术栈,还是PHP,Java技术栈,从事后端开发的朋友对nginx一定不会陌生. 想要深入学习nginx,阅读源码一定是非常重要的一环,但nginx源码量毕竟还是不算少,一不小心就容易 ...

  8. php万能表单源码_震撼!全网第一张源码分析全景图揭秘Nginx

    不管是C/C++技术栈,还是PHP,Java技术栈,从事后端开发的朋友对nginx一定不会陌生. 想要深入学习nginx,阅读源码一定是非常重要的一环,但nginx源码量毕竟还是不算少,一不小心就容易 ...

  9. java asm源码分析_探究CAS原理(基于JAVA8源码分析)

    比较并替换,实现并发算法时常用到的一种技术,在java同步器中大量使用了CAS技术,神奇的实现了多线程执行的安全性 思想很简单:三个参数一个当前内存值V 旧的预期值A 即将更新的值B 当且仅当预期值A ...

最新文章

  1. CXF的webservice接口中字符串参数中文问题
  2. Kubernetes 最佳安全实践指南
  3. 【资讯】人工智能专业职称来了,快来看看你够不够申请资格!
  4. java 遍历类路径
  5. html 指定对象为块元素,html内联(行内)元素、块级(块状)元素和行内块元素分类...
  6. android 网络程序下载,Android之网络文件下载
  7. 基于S3C2440的U-BOOT的start.S分析
  8. Mysql中Innodb大量插入数据时SQL语句的优化
  9. centos8平台用NetworkManager/nmcli管理网络
  10. mysql使用了索引还是慢,`MySQL GROUP BY使用索引时速度较慢
  11. android事件拦截处理机制详解
  12. 如何区分线性系统与时变系统
  13. vue中使用kindeditor编辑器_vue集成kindeditor富文本
  14. layer修改弹框标题样式
  15. OK6410上裸机点亮LED程序
  16. MATLAB 查找互素(质)对
  17. 我,钢铁侠,现急缺660亿美金
  18. JDBC与JAVA程序笔记
  19. 关于SQL Server 无法连接到服务器,远程过程调用失败,网络配置无项目
  20. 将mysql中时间类型的字段导入hive中遇到的坑(时间错啦)

热门文章

  1. php 数组去重且不保留,php数组去重并计数求和如何操作
  2. c# 再次尝试 连接失败_手机投屏电视连接不上怎么回事?
  3. Java基础——注解的初步认识
  4. 递归——黑白棋子的移动(洛谷 P1259)
  5. 計算機二級-java08
  6. datax 不识别字段过滤_静电式空气过滤器有什么特点 静电式空气过滤器特点介绍【详解】...
  7. 华为服务器型号命名,服务器的命名规则
  8. 百兆以太网口通信速率_以太网 数据包速率计算方法
  9. 两万字深度介绍分布式系统原理,这一篇就够了
  10. MongoDB数据库因安全漏洞,导致Family Locator泄露二十多万名用户数据