大家好,我是冰河~~

本文纯干货,从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小。

1.Callable接口介绍

Callable接口是JDK1.5新增的泛型接口,在JDK1.8中,被声明为函数式接口,如下所示。

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

在JDK 1.8中只声明有一个方法的接口为函数式接口,函数式接口可以使用@FunctionalInterface注解修饰,也可以不使用@FunctionalInterface注解修饰。只要一个接口中只包含有一个方法,那么,这个接口就是函数式接口。

在JDK中,实现Callable接口的子类如下图所示。

默认的子类层级关系图看不清,这里,可以通过IDEA右键Callable接口,选择“Layout”来指定Callable接口的实现类图的不同结构,如下所示。

这里,可以选择“Organic Layout”选项,选择后的Callable接口的子类的结构如下图所示。

在实现Callable接口的子类中,有几个比较重要的类,如下图所示。

分别是:Executors类中的静态内部类:PrivilegedCallable、PrivilegedCallableUsingCurrentClassLoader、RunnableAdapter和Task类下的TaskCallable。

2.实现Callable接口的重要类分析

接下来,分析的类主要有:PrivilegedCallable、PrivilegedCallableUsingCurrentClassLoader、RunnableAdapter和Task类下的TaskCallable。虽然这些类在实际工作中很少被直接用到,但是作为一名合格的开发工程师,设置是秃顶的资深专家来说,了解并掌握这些类的实现有助你进一步理解Callable接口,并提高专业技能(头发再掉一批,哇哈哈哈。。。)。

  • PrivilegedCallable

PrivilegedCallable类是Callable接口的一个特殊实现类,它表明Callable对象有某种特权来访问系统的某种资源,PrivilegedCallable类的源代码如下所示。

/**
* A callable that runs under established access control settings
*/
static final class PrivilegedCallable<T> implements Callable<T> {
private final Callable<T> task;
private final AccessControlContext acc; PrivilegedCallable(Callable<T> task) {
this.task = task;
this.acc = AccessController.getContext();
} public T call() throws Exception {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<T>() {
public T run() throws Exception {
return task.call();
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}

从PrivilegedCallable类的源代码来看,可以将PrivilegedCallable看成是对Callable接口的封装,并且这个类也继承了Callable接口。

在PrivilegedCallable类中有两个成员变量,分别是Callable接口的实例对象和AccessControlContext类的实例对象,如下所示。

private final Callable<T> task;
private final AccessControlContext acc;

其中,AccessControlContext类可以理解为一个具有系统资源访问决策的上下文类,通过这个类可以访问系统的特定资源。通过类的构造方法可以看出,在实例化AccessControlContext类的对象时,只需要传递Callable接口子类的对象即可,如下所示。

PrivilegedCallable(Callable<T> task) {
this.task = task;
this.acc = AccessController.getContext();
}

AccessControlContext类的对象是通过AccessController类的getContext()方法获取的,这里,查看AccessController类的getContext()方法,如下所示。

public static AccessControlContext getContext(){
AccessControlContext acc = getStackAccessControlContext();
if (acc == null) {
return new AccessControlContext(null, true);
} else {
return acc.optimize();
}
}

通过AccessController的getContext()方法可以看出,首先通过getStackAccessControlContext()方法来获取AccessControlContext对象实例。如果获取的AccessControlContext对象实例为空,则通过调用AccessControlContext类的构造方法实例化,否则,调用AccessControlContext对象实例的optimize()方法返回AccessControlContext对象实例。

这里,我们先看下getStackAccessControlContext()方法是个什么鬼。

private static native AccessControlContext getStackAccessControlContext();

原来是个本地方法,方法的字面意思就是获取能够访问系统栈的决策上下文对象。

接下来,我们回到PrivilegedCallable类的call()方法,如下所示。

public T call() throws Exception {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<T>() {
public T run() throws Exception {
return task.call();
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}

通过调用AccessController.doPrivileged()方法,传递PrivilegedExceptionAction。接口对象和AccessControlContext对象,并最终返回泛型的实例对象。

首先,看下AccessController.doPrivileged()方法,如下所示。

@CallerSensitive
public static native <T> T
doPrivileged(PrivilegedExceptionAction<T> action,
AccessControlContext context)
throws PrivilegedActionException;

可以看到,又是一个本地方法。也就是说,最终的执行情况是将PrivilegedExceptionAction接口对象和AccessControlContext对象实例传递给这个本地方法执行。并且在PrivilegedExceptionAction接口对象的run()方法中调用Callable接口的call()方法来执行最终的业务逻辑,并且返回泛型对象。

  • PrivilegedCallableUsingCurrentClassLoader

此类表示为在已经建立的特定访问控制和当前的类加载器下运行的Callable类,源代码如下所示。

/**
* A callable that runs under established access control settings and
* current ClassLoader
*/
static final class PrivilegedCallableUsingCurrentClassLoader<T> implements Callable<T> {
private final Callable<T> task;
private final AccessControlContext acc;
private final ClassLoader ccl; PrivilegedCallableUsingCurrentClassLoader(Callable<T> task) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
this.task = task;
this.acc = AccessController.getContext();
this.ccl = Thread.currentThread().getContextClassLoader();
} public T call() throws Exception {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<T>() {
public T run() throws Exception {
Thread t = Thread.currentThread();
ClassLoader cl = t.getContextClassLoader();
if (ccl == cl) {
return task.call();
} else {
t.setContextClassLoader(ccl);
try {
return task.call();
} finally {
t.setContextClassLoader(cl);
}
}
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}
}

这个类理解起来比较简单,首先,在类中定义了三个成员变量,如下所示。

private final Callable<T> task;
private final AccessControlContext acc;
private final ClassLoader ccl;

接下来,通过构造方法注入Callable对象,在构造方法中,首先获取系统安全管理器对象实例,通过系统安全管理器对象实例检查是否具有获取ClassLoader和设置ContextClassLoader的权限。并在构造方法中为三个成员变量赋值,如下所示。

PrivilegedCallableUsingCurrentClassLoader(Callable<T> task) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
this.task = task;
this.acc = AccessController.getContext();
this.ccl = Thread.currentThread().getContextClassLoader();
}

接下来,通过调用call()方法来执行具体的业务逻辑,如下所示。

public T call() throws Exception {
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<T>() {
public T run() throws Exception {
Thread t = Thread.currentThread();
ClassLoader cl = t.getContextClassLoader();
if (ccl == cl) {
return task.call();
} else {
t.setContextClassLoader(ccl);
try {
return task.call();
} finally {
t.setContextClassLoader(cl);
}
}
}
}, acc);
} catch (PrivilegedActionException e) {
throw e.getException();
}
}

在call()方法中同样是通过调用AccessController类的本地方法doPrivileged,传递PrivilegedExceptionAction接口的实例对象和AccessControlContext类的对象实例。

具体执行逻辑为:在PrivilegedExceptionAction对象的run()方法中获取当前线程的ContextClassLoader对象,如果在构造方法中获取的ClassLoader对象与此处的ContextClassLoader对象是同一个对象(不止对象实例相同,而且内存地址也相同),则直接调用Callable对象的call()方法返回结果。否则,将PrivilegedExceptionAction对象的run()方法中的当前线程的ContextClassLoader设置为在构造方法中获取的类加载器对象,接下来,再调用Callable对象的call()方法返回结果。最终将当前线程的ContextClassLoader重置为之前的ContextClassLoader。

  • RunnableAdapter

RunnableAdapter类比较简单,给定运行的任务和结果,运行给定的任务并返回给定的结果,源代码如下所示。

/**
* A callable that runs given task and returns given result
*/
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
  • TaskCallable

TaskCallable类是javafx.concurrent.Task类的静态内部类,TaskCallable类主要是实现了Callable接口并且被定义为FutureTask的类,并且在这个类中允许我们拦截call()方法来更新task任务的状态。源代码如下所示。

private static final class TaskCallable<V> implements Callable<V> {

	private Task<V> task;
private TaskCallable() { } @Override
public V call() throws Exception {
task.started = true;
task.runLater(() -> {
task.setState(State.SCHEDULED);
task.setState(State.RUNNING);
});
try {
final V result = task.call();
if (!task.isCancelled()) {
task.runLater(() -> {
task.updateValue(result);
task.setState(State.SUCCEEDED);
});
return result;
} else {
return null;
}
} catch (final Throwable th) {
task.runLater(() -> {
task._setException(th);
task.setState(State.FAILED);
});
if (th instanceof Exception) {
throw (Exception) th;
} else {
throw new Exception(th);
}
}
}
}

从TaskCallable类的源代码可以看出,只定义了一个Task类型的成员变量。下面主要分析TaskCallable类的call()方法。

当程序的执行进入到call()方法时,首先将task对象的started属性设置为true,表示任务已经开始,并且将任务的状态依次设置为State.SCHEDULED和State.RUNNING,依次触发任务的调度事件和运行事件。如下所示。

task.started = true;
task.runLater(() -> {
task.setState(State.SCHEDULED);
task.setState(State.RUNNING);
});

接下来,在try代码块中执行Task对象的call()方法,返回泛型对象。如果任务没有被取消,则更新任务的缓存,将调用call()方法返回的泛型对象绑定到Task对象中的ObjectProperty对象中,其中,ObjectProperty在Task类中的定义如下。

private final ObjectProperty<V> value = new SimpleObjectProperty<>(this, "value");

接下来,将任务的状态设置为成功状态。如下所示。

try {
final V result = task.call();
if (!task.isCancelled()) {
task.runLater(() -> {
task.updateValue(result);
task.setState(State.SUCCEEDED);
});
return result;
} else {
return null;
}
}

如果程序抛出了异常或者错误,会进入catch()代码块,设置Task对象的Exception信息并将状态设置为State.FAILED,也就是将任务标记为失败。接下来,判断异常或错误的类型,如果是Exception类型的异常,则直接强转为Exception类型的异常并抛出。否则,将异常或者错误封装为Exception对象并抛出,如下所示。

catch (final Throwable th) {
task.runLater(() -> {
task._setException(th);
task.setState(State.FAILED);
});
if (th instanceof Exception) {
throw (Exception) th;
} else {
throw new Exception(th);
}
}

记住:你比别人强的地方,不是你做过多少年的CRUD工作,而是你比别人掌握了更多深入的技能。不要总停留在CRUD的表面工作,理解并掌握底层原理并熟悉源码实现,并形成自己的抽象思维能力,做到灵活运用,才是你突破瓶颈,脱颖而出的重要方向!

最后,作为一名合格(发际线比较高)的开发人员或者资深(秃顶)的工程师和架构师来说,理解原理和掌握源码,并形成自己的抽象思维能力,灵活运用是你必须掌握的技能。

好了,今天就到这儿吧,我是冰河,我们下期见~~

【高并发】深入解析Callable接口的更多相关文章

  1. 高并发之——深入解析Callable接口

    本文纯干货,从源码角度深入解析Callable接口,希望大家踏下心来,打开你的IDE,跟着文章看源码,相信你一定收获不小. 1.Callable接口介绍 Callable接口是JDK1.5新增的泛型接 ...

  2. 高并发秒杀系统--Service接口设计与实现

    [DAO编写之后的总结] DAO层    -->    接口设计 + SQL编写 DAO拼接等逻辑    -->    统一在Service层完成 [Service层的接口设计] 1.接口 ...

  3. 【高并发】两种异步模型与深度解析Future接口

    大家好,我是冰河~~ 本文有点长,但是满满的干货,以实际案例的形式分析了两种异步模型,并从源码角度深度解析Future接口和FutureTask类,希望大家踏下心来,打开你的IDE,跟着文章看源码,相 ...

  4. 聊聊高并发(三十九)解析java.util.concurrent各个组件(十五) 理解ExecutorService接口的设计

    上一篇讲了Executor接口的设计,目的是将任务的运行和任务的提交解耦.能够隐藏任务的运行策略.这篇说说ExecutorService接口.它扩展了Executor接口,对Executor的生命周期 ...

  5. 【高并发】深度解析ScheduledThreadPoolExecutor类的源代码

    在[高并发专题]的专栏中,我们深度分析了ThreadPoolExecutor类的源代码,而ScheduledThreadPoolExecutor类是ThreadPoolExecutor类的子类.今天我 ...

  6. 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

    前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...

  7. Java 面试知识点解析(二)——高并发编程篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  8. 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类

    这篇说说java.util.concurrent.atomic包里的类,总共12个.网上有非常多文章解析这几个类.这里挑些重点说说. watermark/2/text/aHR0cDovL2Jsb2cu ...

  9. SSD磁盘,CPU居高不下,高并发的情况下,是不是mysql解析器耗费的cpu资源高?

    你看看我做的实验,这个user表是300多W纪录,普通磁盘下,消耗时间最多的是Copy to tmp table 0.81秒,当然在ssd下,这个可以减少很多很多的,第二高就是sending data ...

随机推荐

  1. Linux系列(25) - 常用快捷键(未更新完)

    快捷键 说明 Ctrl+L 清屏 tab tab按一次自动补全目录文件名称/tab按二次将目录下带有补全前面字段的所有文件目录展示出来,例子: cd /   tab键按两次将根目录下所有文件展示出来 ...

  2. CF280D-k-Maximum Subsequence Sum【模拟费用流,线段树】

    正题 题目链接:https://www.luogu.com.cn/problem/CF280D 题目大意 一个长度为\(n\)的序列,\(m\)次操作 修改一个数 询问一个区间中选出\(k\)段不交子 ...

  3. CF19E-Fairy【树形结构,差分】

    正题 题目链接:https://www.luogu.com.cn/problem/CF19E 题目大意 给出\(n\)个点\(m\)条边的一张无向图,求有多少条边去掉后可以使得图变成一张二分图. \( ...

  4. CF1370F2-The Hidden Pair(Hard Version)【交互题,二分】

    正题 题目链接:https://www.luogu.com.cn/problem/CF1370F2 题目大意 \(T\)组数据,给出\(n\)个点的一棵树,有两个隐藏的关键点.你每次可以询问一个点集, ...

  5. 数据库建表权限 CREATE command denied to user for table

    今天在表中用Navicat连接服务器上的mysql账号进行建表,报了个这样类似的错, CREATE command denied to user for table 是数据库权限设置的问题,所以无法进 ...

  6. ubuntu20.04安装网易云音乐

    Ubuntu20.04安装网易云 进入网易云音乐下载地址 下载对应客户端 进入终端,安装 sudo dpkg -i 软件名.deb

  7. 一个关于MySQL指定编码实现的小坑

    写在前面 环境:MySQL5.7+,MySQL数据库字符编码实现为utf8,表也为utf8 场景:微信授权获取用户信息(包括昵称)并保存到数据库,有的用户成功了,少数用户却失败了 那么为什么会失败呢? ...

  8. 洛谷4248 AHOI2013差异 (后缀数组SA+单调栈)

    补博客! 首先我们观察题目中给的那个求\(ans\)的方法,其实前两项没什么用处,直接\(for\)一遍就求得了 for (int i=1;i<=n;i++) ans=ans+i*(n-1); ...

  9. 洛谷4475 巧克力王国(KD-Tree + 维护子树和)

    (嘤嘤嘤 又是一个自闭了一晚上的题) qwq果然不是平面上的点的问题,也可以直接用KDTree打暴力 我们对于巧克力直接建kdtree 维护一个\(mx[i],mn[i]\) 但是有一个非常不友好的事 ...

  10. ArrayList-源码分析-自动扩容机制

    ArrayList类: public class ArrayList....{ ...... private static final int DEFAULT_CAPACITY = 10; //默认容 ...