1. Introduction to the Callable interface

The Callable interface is a new generic interface added in JDK1.5 and declared as a functional interface in JDK1.8 as follows.

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

In JDK 1.8, an interface with only one method declared is a functional interface. A functional interface can be modified with or without the @FunctionalInterface annotation. As long as an interface contains only one method, then the interface is a functional interface.

In the JDK, the subclasses that implement the Callable interface are shown in the following figure.

Callable

The default subclass hierarchy diagram is not visible. Here, you can specify the different structures of the Callable interface implementation class diagram by right-clicking the Callable interface in IDEA and selecting “Layout”, as shown below.

idea

Here, you can select the “Organic Layout” option, and the structure of the subclasses of the Callable interface after selection is shown below.

sobyte

Among the subclasses that implement the Callable interface, there are several more important classes, as shown in the following figure.

sobyte

They are: static internal classes in the Executors class: PrivilegedCallable, PrivilegedCallableUsingCurrentClassLoader, RunnableAdapter and TaskCallable under the Task class.

2. Analysis of important classes that implement the Callable interface

The next classes to be analyzed are: PrivilegedCallable, PrivilegedCallableUsingCurrentClassLoader, RunnableAdapter and TaskCallable under the Task class. As a qualified development engineer, or even a senior expert, knowing and implementing these classes will help you further understand the Callable interface and improve your professional skills.

PrivilegedCallable

The PrivilegedCallable class is a special implementation of the Callable interface, which indicates that the Callable object has some privilege to access certain resources of the system.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * 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();
        }
    }
}

From the source code of the PrivilegedCallable class, PrivilegedCallable can be viewed as a wrapper around the Callable interface, and this class also inherits the Callable interface.

There are two member variables in the PrivilegedCallable class, which are the instance object of the Callable interface and the instance object of the AccessControlContext class, as shown below.

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

Among them, the AccessControlContext class can be understood as a context class with system resource access decisions, through which you can access specific resources of the system. As can be seen through the class constructor, when instantiating an object of the AccessControlContext class, you only need to pass the object of the Callable interface subclass, as follows.

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

The object of the AccessControlContext class is obtained through the getContext() method of the AccessController class. Here, check the getContext() method of the AccessController class, as follows.

1
2
3
4
5
6
7
8
public static AccessControlContext getContext(){
    AccessControlContext acc = getStackAccessControlContext();
    if (acc == null) {
        return new AccessControlContext(null, true);
    } else {
        return acc.optimize();
    }
}

Through the AccessController getContext () method can be seen, first through the getStackAccessControlContext () method to get the AccessControlContext object instance. If the AccessControlContext object instance obtained is empty, it is instantiated by calling the AccessControlContext class constructor method, otherwise, the AccessControlContext object instance is called optimize() method to return the AccessControlContext object instance.

Here, we first look at the getStackAccessControlContext () method is a ghost.

1
private static native AccessControlContext getStackAccessControlContext();

It turns out to be a local method, and the method literally means to get the decision context object that can access the system stack.

Next, we go back to the call() method of the PrivilegedCallable class, as shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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();
    }
}

By calling AccessController.doPrivileged() method, pass PrivilegedExceptionAction. interface object and AccessControlContext object, and finally return the instance object of the generic type.

First, take a look at the AccessController.doPrivileged() method, which is shown below.

1
2
3
4
5
@CallerSensitive
public static native <T> T
    doPrivileged(PrivilegedExceptionAction<T> action,
                 AccessControlContext context)
    throws PrivilegedActionException;

As you can see, it is another local method. That is, the final implementation is to pass the PrivilegedExceptionAction interface object and the AccessControlContext object instance to this local method for execution. And the call() method of the Callable interface is called in the run() method of the PrivilegedExceptionAction interface object to execute the final business logic and return the generic object.

PrivilegedCallableUsingCurrentClassLoader

This class is represented as a Callable class running under an established specific access control and the current class loader, and the source code is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * 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();
        }
    }
}

This class is relatively simple to understand. First, there are three member variables defined in the class, as follows.

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

Next, inject the Callable object through the constructor method, and in the constructor method, first get the System Security Manager object instance, and check whether it has the permission to get the ClassLoader and set the ContextClassLoader through the System Security Manager object instance. And assign values to the three member variables in the constructor method, as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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();
}

Next, the specific business logic is executed by calling the call() method, as shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
    }
}

In the call() method is also by calling the local method doPrivileged of the AccessController class, passing the PrivilegedExceptionAction interface instance object and the AccessControlContext class object instance.

The specific execution logic is: in the PrivilegedExceptionAction object run() method to obtain the current thread of the ContextClassLoader object, if the ClassLoader object obtained in the constructor method and the ContextClassLoader object here is the same object (more than If the ClassLoader object obtained in the constructor method and the ContextClassLoader object here is the same object (not only the same object instance, but also the same memory address), then the call() method of the Callable object is called directly to return the result. Otherwise, set the ContextClassLoader of the current thread in the run() method of the PrivilegedExceptionAction object to the class loader object obtained in the constructor method, and then call the call() method of the Callable object to return the result. Finally, the ContextClassLoader of the current thread is reset to the previous ContextClassLoader.

RunnableAdapter

RunnableAdapter class is relatively simple, given the task to run and the result, run the given task and return the given result, the source code is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/**
 * 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 class is a static internal class of javafx.concurrent. The TaskCallable class mainly implements the Callable interface and is defined as a FutureTask class and allows us to intercept the call() method to update the status of the task. The source code is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);
            }
        }
    }
}

As you can see from the source code of TaskCallable class, only one member variable of Task type is defined. The following is an analysis of the call() method of the TaskCallable class.

When the execution of the program enters the call() method, the start property of the task object is set to true, indicating that the task has started, and the state of the task is set to State.SCHEDULED and State.RUNNING in order to trigger the scheduling and running events of the task. As shown below.

1
2
3
4
5
task.started = true;
task.runLater(() -> {
    task.setState(State.SCHEDULED);
    task.setState(State.RUNNING);
});

Next, the call() method of the Task object is executed in the try code block, returning the generic object. If the task is not cancelled, the task’s cache is updated and the generic object returned by the call() method is bound to the ObjectProperty<V> object in the Task object, where ObjectProperty<V> is defined in the Task class as follows.

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

Next, set the status of the task to Success. This is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
try {
    final V result = task.call();
    if (!task.isCancelled()) {
        task.runLater(() -> {
            task.updateValue(result);
            task.setState(State.SUCCEEDED);
        });
        return result;
    } else {
        return null;
    }
}

If the program throws an exception or error, it will enter the catch() block, set the Exception message of the Task object and set the state to State.FAILED, which means the task will be marked as failed. Next, determine the type of the exception or error, and if it is an Exception type exception, it is directly and strongly converted to an Exception type exception and thrown. Otherwise, the exception or error is wrapped as an Exception object and thrown, as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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);
    }
}