Migrating applications that use concurrency and asynchronous programming models to Liberty

You can migrate applications that use Concurrency Utilities for Java™ EE, Asynchronous beans, and CommonJ Timer and Work Manager from WebSphere® Application Server traditional to WebSphere Application Server Liberty.

If you are migrating to Liberty 21.0.0.12 or earlier, the Asynchronous beans, CommonJ Timer, and Work Manager APIs are not available. You must update your applications to use the Concurrency Utilities for Java EE programming model instead. For more information, see Example API tasks that implement Concurrency Utilities for Java EE.

Differences in configuration and threading

WebSphere Application Server traditional provides a number of configuration options for Concurrency Utilities for Java EE related to thread pooling, which are configured differently or not available in Liberty. Liberty has a single common thread pool across all managed executors and managed scheduled executors that are also shared by Liberty components. This allows Liberty to optimize thread management. In WebSphere Application Server traditional, each work manager can be configured with two thread pools. One thread pool is for tasks that are submitted to run as soon as possible (submit/execute/invoke methods of managed executor) and has a configurable work request queue and action to take when the queue capacity is exceeded. The other thread pool is for scheduled tasks (schedule methods of managed scheduled executor).

If you plan to use Concurrency Utilities for Java EE with separate thread pools in Liberty, various approaches can be taken to achieve behavior similar to the configuration options that are available in WebSphere Application Server traditional.

Use concurrency policy configuration

Use the concurrencyPolicy element to replicate some of the behavior from WebSphere Application Server traditional in Liberty. The following table lists the available configuration options for the concurrencyPolicy element and how this element compares to WebSphere Application Server traditional. The concurrencyPolicy configuration does not apply to scheduled tasks.

Table 1.
Liberty managed executors concurrencyPolicy WebSphere Application Server traditional work manager and managed executors
expedite Minimum threads
max Maximum threads
maxQueueSize Work Request Queue Size
maxWaitForEnqueue Work Request Queue Full Action = WAIT
runIfQueueFull = false Work Request Queue Full Action = FAIL
startTimeout Start timeout
longRunningPolicyRef + ManagedTask.LONGRUNNING_HINT Daemon work

Constructing a Java SE executor around a managed thread factory

The java.util.concurrent package of Java SE provides several ways to construct thread pools as executors and scheduled executors around a specific thread factory. A managed thread factory can be supplied in place of an unmanaged thread factory, producing a thread pool that runs tasks on managed threads. Unlike the managed executor and managed scheduled executors, the managed threads that are pooled by these executors run with the thread context from when the managed thread factory was looked up, rather than from when the task was submitted or scheduled. Thread context also remains on the managed thread for the life of thread, which reduces thread context switching overhead.

Example replacement for maximum work threads and work queue configuration

<managedExecutorService jndiName="concurrent/execSvc">
    <concurrencyPolicy max="2" maxQueueSize="3" runIfQueueFull="false" maxWaitForEnqueue="0"/>
</managedExecutorService>

Example replacement for maximum alarm threads

int maxAlarms = 2;
ManagedThreadFactory threadFactory =
    (ManagedThreadFactory) new InitialContext().lookup(
        "java:comp/DefaultManagedThreadFactory");
ScheduledExecutorService executor =
    Executors.newScheduledThreadPool(maxAlarms, threadFactory);
Callable<Integer> task = new MyTask();
ScheduledFuture<Integer> future = executor.schedule(
    task, 50, TimeUnit.SECONDS);
int result = future.get();

Applying a start timeout

<managedExecutorService jndiName="concurrent/execSvc">
    <concurrencyPolicy startTimeout="1m"/>
</managedExecutorService>

Applying a work timeout

Use WebSphere Application Server traditional to configure a work timeout that applies to tasks that you submit to run as soon as possible (submit/execute/invoke methods). If a task runs for longer than the allotted work timeout, the program tries to cancel the task by interrupting it. Liberty does not have work timeout as a configuration option, but you can implement a similar behavior by submitting the task with a managed task listener. In response to the taskStarting notification, the managed task listener schedules a task to cancel the Future method. Alternately, when you use a thread pool executor where a managed task listener is not available, you can override the thread pool executor beforeExecute method to schedule a task that interrupts the thread of execution.

Example replacement for work timeout

ManagedExecutorService executor =
    (ManagedExecutorService) new InitialContext().lookup(
        "java:comp/DefaultManagedExecutorService");
Callable<Long> slowTask = new SlowTask();
slowTask = ManagedExecutors.managedTask(
           slowTask, new WorkTimeout(5, TimeUnit.SECONDS));
Future<Long> future = executor.submit(slowTask);
try {
    long result = future.get(1, TimeUnit.MINUTES);
    // task successful...
} catch (CancellationException x) {
    // task was canceled, possibly due to timeout
}

public class WorkTimeout implements ManagedTaskListener {
    private final long timeout;
    private final TimeUnit unit;

    public WorkTimeout(long timeout, TimeUnit unit) {
        this.timeout = timeout;
        this.unit = unit;
    }

    public void taskSubmitted(Future<?> future,
        ManagedExecutorService executor, Object task) {}

    public void taskStarting(final Future<?> future,
        ManagedExecutorService executor, Object task) {
        try {
            ScheduledExecutorService scheduledExecutor =
                (ScheduledExecutorService) new InitialContext().lookup(
                    "java:comp/DefaultManagedScheduledExecutorService");
            scheduledExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    if (!future.isDone())
                        future.cancel(true);
                }
            }, timeout, unit);
        } catch (NamingException x) {
            x.printStackTrace(System.out);
        }
    }

    public void taskAborted(Future<?> future, ManagedExecutorService executor, Object task, Throwable x) {}

    public void taskDone(Future<?> future, ManagedExecutorService executor, Object task, Throwable x) {}
}

Example of daemon work


Configuration:
<concurrencyPolicy id="longRunning" max="2"/>
<managedExecutorService jndiName="concurrent/execSvc" longRunningPolicyRef="longRunning" />

Application code:
ManagedExecutorService executor =
 (ManagedExecutorService) new InitialContext().lookup(
   "concurrent/execSvc");
Map<String, String> props = Collections.singletonMap(ManagedTask.LONGRUNNING_HINT, 
Boolean.TRUE.toString());
Callable<Long> slowTask = ManagedExecutors.managedTask(new SlowTask(), props, null);
Future<Long> future = executor.submit(slowTask);