[high concurrency] deeply analyze the Callable interface

Keywords: Concurrent Programming

Hello, I'm glacier~~

This article is pure dry goods. It deeply analyzes the Callable interface from the perspective of source code. I hope you can step down, open your IDE, and follow the article to see the source code. I believe you will gain a lot.

1. Introduction to callable interface

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, the interface with only one method is declared as a functional interface. The functional interface can be modified with or without @ FunctionalInterface annotation. As long as an interface contains only one method, the interface is a functional interface.

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

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

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

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

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

2. Analysis of important classes for implementing Callable interface

Next, the classes analyzed mainly include: PrivilegedCallable, PrivilegedCallableUsingCurrentClassLoader, RunnableAdapter and TaskCallable under Task class. Although these classes are rarely used directly in practical work, as a qualified development engineer and a bald senior expert, understanding and mastering the implementation of these classes will help you further understand the Callable interface and improve your professional skills (another batch of hair will fall off, wahahaha...).

  • PrivilegedCallable

The privilegedcalable 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 privilegedcalable class is as follows.

/**
 * 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 privilegedcalable class, you can regard privilegedcalable as a package of the Callable interface, and this class also inherits the Callable interface.

There are two member variables in the privilegedcalable 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, AccessControlContext class can be understood as a context class with system resource access decision, through which specific resources of the system can be accessed. It can be seen from the class construction method that when instantiating the object of AccessControlContext class, you only need to pass the object of the subclass of Callable interface, as shown below.

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

The object of AccessControlContext class is obtained through the getContext() method of AccessController class. Here, view the getContext() method of AccessController class, as shown below.

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

As can be seen from the getContext() method of AccessController, first obtain the AccessControlContext object instance through the getstackaccesscontrocontext () method. If the obtained AccessControlContext object instance is empty, instantiate it by calling the constructor of AccessControlContext class. Otherwise, call the optimize() method of AccessControlContext object instance to return the AccessControlContext object instance.

Here, let's see what the getstackaccesscontrocontext () method is.

private static native AccessControlContext getStackAccessControlContext();

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

Next, we return to the call() method of the privilegedcalable 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 the privilegedexception action by calling the AccessController.doPrivileged() method. Interface object and AccessControlContext object, and finally return the generic instance object.

First, 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, this is another local method. In other words, the final execution is to pass the instance of the privileged exceptionaction interface object and AccessControlContext object to this local method for execution. And the call() method of the Callable interface is invoked 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 the established specific access control and the current class loader. The source code is as follows.

/**
 * 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, 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, the Callable object is injected through the construction method. In the construction method, first obtain the system security manager object instance, and check whether it has the permission to obtain ClassLoader and set ContextClassLoader through the system security manager object instance. And assign values to three member variables in the construction method, 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, execute the specific business logic 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 instance object of the privileged exceptionaction interface and the object instance of the AccessControlContext class are also passed by calling the local method doPrivileged of the AccessController class.

The specific execution logic is: get the ContextClassLoader object of the current thread in the run () method of the privilegedexception 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 also the memory address is the same), 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 privilegedexeptionaction object as 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

The RunnableAdapter class is relatively simple. Given the running task and result, run the given task and return the given result. The source code is as follows.

/**
 * 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.Task class. TaskCallable class is mainly a class that implements the Callable interface and is defined as FutureTask. In this class, we can intercept the call() method to update the status of task tasks. The source code is as follows.

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 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 attribute of the task object to true, indicating that the task has started, and set the status of the task to State.SCHEDULED and State.RUNNING in turn to trigger the scheduling event and running event of the task in turn. As shown below.

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 to return the generic object. If the Task is not cancelled, the cache of the Task is updated, and the generic object returned by calling the call() method is bound to the ObjectProperty object in the Task object, where the ObjectProperty is defined in the Task class as follows.

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

Next, set the status of the task to success. As shown below.

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 Exception or error. If it is an Exception of type Exception, directly convert it to an Exception of type Exception and throw it. Otherwise, the Exception or error is encapsulated as an Exception object and thrown, 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 how many years of CRUD work you have done, but that you have mastered more in-depth skills than others. Don't always work on the surface of CRUD. Understanding and mastering the underlying principles, being familiar with the source code implementation, forming your own abstract thinking ability and being flexible are the important directions for you to break through the bottleneck and stand out!

Finally, as a qualified (high hairline) developer or senior (bald) engineer and architect, understanding the principle and mastering the source code, forming your own abstract thinking ability and flexible application are the skills you must master.

Well, that's all for today. I'm glacier. I'll see you next time~~

Posted by PaulRyan on Sun, 07 Nov 2021 20:36:43 -0800