头图

Hello everyone, I am Glacier~~

This article is purely dry, and analyzes the Callable interface in depth from the source code point of view. I hope everyone will step down, open your IDE, and follow the article to read the source code. I believe you must have gained a lot.

1. Callable interface introduction

The Callable interface is a new generic interface in JDK1.5. In JDK1.8, it is declared as a functional interface, as shown below.

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

In JDK 1.8, only an interface with one method is declared as a functional interface. The functional interface can be decorated with the @FunctionalInterface annotation or not with 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 figure below.

The default subclass hierarchy diagram is not clear. Here, you can use IDEA to right-click the Callable interface and select "Layout" to specify the different structures of the Callable interface implementation class diagram, as shown below.

Here, you can select the "Organic Layout" option, and the selected subclass structure of the Callable interface is shown in the figure below.

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

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

2. Analysis of important classes that implement the Callable interface

Next, the main classes analyzed are: PrivilegedCallable, PrivilegedCallableUsingCurrentClassLoader, RunnableAdapter and TaskCallable under the Task class. Although these classes are rarely used directly in actual work, as a qualified development engineer and a senior expert with a bald setting, understanding and mastering the implementation of these classes will help you further understand the Callable interface and improve your professionalism. Skills (one more hair loss, wow ha ha ha...).

  • PrivilegedCallable

The PrivilegedCallable class is a special implementation class of the Callable interface. It indicates that the Callable object has certain privileges to access certain resources of the system. The source code of the PrivilegedCallable class is shown below.

/**
 * 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();
        }
    }
}

Judging from the source code of the PrivilegedCallable class, PrivilegedCallable can be seen as an encapsulation of the Callable interface, and this class also inherits the Callable interface.

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

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 specific resources of the system can be accessed. It can be seen from the construction method of the class that when instantiating an object of the AccessControlContext class, you only need to pass the object of the Callable interface subclass, as shown below.

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 shown below.

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

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

Here, let's first look at what the hell is the getStackAccessControlContext() method.

private static native AccessControlContext getStackAccessControlContext();

It turned out to be a local method. The literal meaning of a method is to obtain a decision context object that can access the system stack.

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

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();
    }
}

Pass PrivilegedExceptionAction by calling AccessController.doPrivileged() method. Interface object and AccessControlContext object, and finally return a generic instance object.

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

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

As you can see, it is a local method again. In other words, the final execution is to pass the PrivilegedExceptionAction interface object and AccessControlContext object instance to this local method for execution. And call the call() method of the Callable interface 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 that runs under the established specific access control and the current class loader. The source code is shown below.

/**
 * 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 of all, three member variables are defined in the class, as shown below.

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

Next, inject the Callable object through the construction method. In the construction method, first obtain the system security manager object instance, and check whether it has the authority to obtain the ClassLoader and set the ContextClassLoader through the system security manager object instance. And assign values to the three member variables in the constructor, as shown below.

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.

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, the local method doPrivileged of the AccessController class is also called, passing the instance object of the PrivilegedExceptionAction interface and the object instance of the AccessControlContext class.

The specific execution logic is: Get the ContextClassLoader object of the current thread in the run() method of the PrivilegedExceptionAction object. If the ClassLoader object obtained in the construction method is the same object as the ContextClassLoader object here (not only the object instance is the same, but the memory address is also the same) Same), then directly call the call() method of the Callable object 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, 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

The RunnableAdapter class is relatively simple. Given running tasks and results, run the given tasks and return the given results. The source code is shown below.

/**
 * 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

The TaskCallable class is a static internal class of the javafx.concurrent.Task class. The TaskCallable class mainly implements the Callable interface and is defined as a FutureTask class, and in this class allows us to intercept the call() method to update the state of the task task. The source code is shown below.

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 can be seen from the source code of the TaskCallable class, only one member variable of the Task type is defined. The following mainly analyzes the call() method of the TaskCallable class.

When the execution of the program enters the call() method, first set the started property of the task object to true, indicating that the task has started, and set the state of the task to State.SCHEDULED and State.RUNNING in turn to trigger the task's scheduling event in turn And running events. As follows.

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

Next, execute the call() method of the Task object in the try code block and return the generic object. If the task is not cancelled, update the cache of the task, and bind the generic object returned by the call() method to the ObjectProperty<V> object in the Task object, where ObjectProperty<V> is defined as follows in the Task class .

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

Next, set the status of the task to a successful status. As follows.

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() code block, set the Exception information of the Task object and set the state to State.FAILED, that is, mark the task as failed. Next, determine the type of the exception or error. If it is an Exception type exception, it will be directly converted to an Exception type exception and thrown. Otherwise, encapsulate the exception or error as an Exception object and throw it as shown below.

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);
    }
}

Remember: What makes you better than others is not that you have done CRUD work for many years, but that you have more in-depth skills than others. Don't always stay on the surface of CRUD, understand and master the underlying principles and be familiar with the source code implementation, and form your own abstract thinking ability, and use it flexibly. This is the important direction for you to break through the bottleneck and stand out!

Finally, as a qualified (high hairline) developer or senior (balding) engineer and architect, you must understand the principles and master the source code, and form your own abstract thinking ability. Flexible use is a must. Skills.

here today, I’m Glacier, see you in the next issue~~


冰河
156 声望970 粉丝