Exploring Concurrency in Modern Software Development
Summary
Concurrency is difficult to model and conceptualize. In this post, I am going to discuss how I conceptualize concurrency when writing software. I will use Java and TypeScript for my examples.

Concurrency vs Parallelism
In Computer Science, a distinction is made between concurrency and parallelism. For the purposes of this article, I am mostly interested in exploring concurrency as it can be understood as the superset of both concepts (concurrent execution units can be carried out in parallel or serially. Any execution units running in parallel by definition will be concurrent.)
Parallelism is an emphasis on multiple processing streams occurring literally at the same point in time on different hardware.
Concurrency models execution units that might happen out of order—whether it happens in parallel or serially can be understood as an implementation detail.
It is also worth discussing the somewhat more ambiguous terms “synchronous” and “asynchronous.”
Synchronous code often refers to sequential lines of a high-level programming language that execute in order in the same thread.
Asynchronous code often refers to sequential lines of a high-level programming language that execute in some way out-of-order, often not on the same thread and sometimes with other lines executing interleaved with the original lines in the same thread.
In my observation, many software developers do not find it particularly difficult to understand parallelism intuitively. After all, that is how much of the real world behaves. Who has not stood in line at a grocery store and watched multiple queues proceed at the same time but at different paces for each cashier?
In contrast, concurrency, synchronous, and asynchronous code are more subtle and many programming languages expose interfaces to allow generic forms of concurrency—whether that physically happens in parallel or whether the programmer has much influence in the actual order of execution. However, in languages such as Java and TypeScript, how concurrency is expressed may not always be obvious when reading code.
Exploring Concurrency in Java
To start off, we’re going to investigate an example of asynchronous coding in Java. Below is a small example of how concurrency might be accomplished in Java.
1Future<Integer> myAsyncAdder(int a, int b) {
2 return CompletableFuture.runAsync(() -> {
3 Thread.sleep(1000);
4 return a + b;
5 })
6}
7
8var result = myAsyncAdder(1, 2).get();
9System.out.println(result);
What happens in the code above?
Let’s start by focusing on the lines that will be sequentially executed in the same thread in order highlighted below:
1Future<Integer> myAsyncAdder(int a, int b) {
2 return CompletableFuture.runAsync(() -> {
3 Thread.sleep(1000);
4 return a + b;
5 })
6}
7
8var result = myAsyncAdder(1, 2).get();
9System.out.println(result);
One way in which we can understand this code is by imagining that the highlighted lines execute sequentially. We’ll call this the “Synchronous” code.
What about lines 3 and 4 then?
They are part of a lambda expression that will be executed at some point but not as part of the immediate execution flow.
From the perspective of the code flow which starts at the top of this code snippet, it does
not yet matter when that happens and we should understand it as happening
“later”. The method
is an instruction requesting that the provided lambda be executed out-of-order. Internally, it
will be placed on some queue of work that can be picked up by another thread to execute later.
It is possible that execution actually happens in parallel and before we get to line 8;
it is just as possible that it happens much later.runAsync()
Is this the “Asynchronous” code then? From one frame of reference, yes! When reading the original block of code we typically read each line one-by-one, mentally incrementing a program counter to represented the simulated state of the executing computer. As part of that exercise, it is important to mentally excise this code from the sequentially executed code invoked when the code is first encountered. Again, it will execute, “later”.
3 Thread.sleep(1000);
4 return a + b;
Look at lines 3 and 4 on their own though. Is this code “Synchronous” now? Sure! Each line will execute serially from whatever context the lambda is executed later.
The
is also executed in the same thread and will (depending on the JVM’s environment) suspend
the execution of the thread and instruct the OS to not re-schedule the thread and continue execution
until 1000 milliseconds have elapsed.Thread.sleep()
Communicating Between Concurrent Work Units
Now let’s return to this interesting line which needs further investigation:
8 var result = myAsyncAdder(1, 2).get();
Why is the
method call necessary? The get()
method synchronously
returns a myAsyncAdder()
which is used to track an asynchronous execution unit (similar to JavaScript’s Future<V>
discussed in the next section.) The holder of a Promise
can inspect it to determine if that work is finished yet. But what if—from our current
program counter—we just want to wait until that work is done before we continue?Future<V>
In general, we have two approaches:
- Defer the execution of another asynchronous work unit until the
completes and the current program counter will just continue onto the next lines (demonstrated in the next section.)Future<V>
- Block the current execution thread until that asynchronous, concurrent work completes.
The
method call is an instance of the second case. The current thread will invoke get()
and will not be scheduled by the OS until later (depending on exact implementation, there may
be some combination of signaller and spin lock that will re-schedule the thread either periodically
or once the park()
has completed. See OpenJDK’s implementation for an example.)Future<V>
From this, I think it can be shown that labelling code as asynchronous is often misleading because it depends on the frame of reference.
Multiple Async Steps
Let us move onto a more complicated example. This example will be somewhat intentionally obtuse, in order to highlight the concurrency.
1const myAsyncMather = async (a: number, b: number) => {
2 const value = a + b;
3
4 return await sleep(1000)
5 .then(async () => {
6 return value * 2;
7 })
8}
9
10myAsyncMather(1, 2).then(result => {
11 console.log(result);
12});
Note that the code above depends on a small helper function
for succinctness, but I won‘t spend anymore time on this—JavaScript does not provide
a builtin function that behaves like this function. Just assume that sleep()
is an sleep()
function. A possible implementation is below:async
1const sleep = (duration: number) =>
2 new Promise(resolve => setTimeout(resolve, duration));
Multiple Asynchronous Patterns
Let‘s start to parse the sequence of events in this example. While I find the TypeScript syntax more elegant than the Java syntax, there is a lot more going on in this code block than in the Java example.
The Async Operator
To begin, a
is a JavaScript object that is created by the runtime (although it can also be created manually, such as in our example Promise
function) which can be used to track an asynchronous
execution unit just like Java’s sleep()
. If we wanted to, we could pass
the Future<V>
around as an indicator of the result of asynchronous work, just like
we did in Java.Promise
To relate asynchronous execution units, invoking
on a then()
instructs the runtime to execute the function passed as the argument to Promise
after the original then()
has completed executing its function’s body. The invocation of Promise
also returns a new then()
which will complete once both steps have completed. This creates a relatively
convenient method of chaining asynchronous work units in a way that visually looks similar to
normal in-order executing code.Promise
That similarity can be deceptive though.
Modern JavaScript provides the
operator as syntax sugar to simplify
the construction and handling of async
s. A function marked with Promise
instructs the JavaScript runtime to effectively modify the function to return a async
which itself will return the result of whatever the Promise
function returns.async
In effect the following code:
1async () => {
2 return 'myValue';
3}
might become something like:
1() => {
2 return new Promise(resolve => resolve('myValue'));
3}
Note that the body of an
function is not necessarily executed
asynchronously (i.e. out of order and sometime in the future). For example, the following code:async
1const asyncLog = async () => {
2 console.log('log from async function');
3};
4
5asyncLog();
6console.log('log from outside function'
will always print:
1log from async function
2log from outside function
On its own, the
just wraps the return value in a async
and a Promise
is just a convenient object used to indicate the result of a possibly-asynchronous
operation. A Promise
can be constructed in the “completed” state.
Until code within the Promise
function instructs the runtime to execute something
asynchronously, everything within the function body will execute synchronously before it returns.async
Where the
function becomes interesting is when the async
operator is employed within the body of the function. Recall that earlier patterns of asynchronous
code execution essentially provided instructions to the runtime to, “run this code block
later at some point,” and then code execution continued immediately after that point. The await
instructs the runtime to return a await
and then
the remainder of the function after the line executed with Promise
will continue
execution after the asynchronous operation completes.await
To illustrate that, the following example will print
immediately after this function is executed and returning from function
will print at least 1 second later.I am awake
1async () => {
2 sleep(1000).then(() => console.log('I am awake'));
3 console.log('returning from function')
4}
In the nearly-identical function below which employs an
operator, the
function will return a await
immediately after Promise
is invoked and sleep()
will not be logged until all the asynchronous steps on line 2 have completed.returning from function
1async () => {
2 await sleep(1000).then(() => console.log('I am awake'));
3 console.log('returning from function')
4}
The function above could be conceptualized as if it were being re-written by the runtime to look like this:
1async () => {
2 return sleep(1000).then(() => console.log('I am awake'))
3 .then(() => {
4 console.log('returning from function');
5 });
6}
Returning to the original code, hopefully we can start to parse out the messy mixture of concurrent work units.
1const myAsyncMather = async (a: number, b: number) => {
2 const value = a + b;
3
4 return await sleep(1000)
5 .then(async () => {
6 return value * 2;
7 })
8}
9
10myAsyncMather(1, 2).then(result => {
11 console.log(result);
12});
The first execution unit will comprise the highlighted code below. Note that in particular,
a few lines such as 5 and 10 are deliberately highlighted. As this code is executed
synchronously, the lambdas will be declared at the same time. For example,
will immediately return a sleep()
representing the asynchronous, “work,”
to be completed in 1 second. With the returned Promise
object, the instance method Promise
is immediately invoked and consequently the lambda
passed to it must be immediately declared as well. However, the lambdas will not be invoked until later.then()
1const myAsyncMather = async (a: number, b: number) => {
2 const value = a + b;
3
4 return await sleep(1000)
5 .then(async () => {
6 return value * 2;
7 })
8}
9
10myAsyncMather(1, 2).then(result => {
11 console.log(result);
12});
This execution will complete and then sometime after this the
to sleep
for 1 second completes, the event loop will pick up and run the next execution unit which is
the highlighted code below:Promise
5 .then(async () => {
6 return value * 2;
7 })
Even though this second execution unit is not internally taking any asynchronous actions, there will still be a final, distinct, asynchronous execution unit shown below:
10myAsyncMather(1, 2).then(result => {
11 console.log(result);
12});
Note also that in this case, all these concurrent execution units must happen sequentially
(even if not immediately after one another) because they were chained together with
. That imposes an ordering that is not always
going to exist with then()
functions though.async
“Later” is a Matter of Perspective
Now that we have reviewed two examples of concurrency in TypeScript and Java, what does it mean to say that code is “asynchronous”? It is really a matter of perspective and—depending on the language—it can sometimes be hard to tell.
Viewed in isolation, there is nothing asynchronous/concurrent about this line below. When the program counter reaches it—however that may happen—we can consider the execution of this function body as a synchronous, independent execution unit.
5 .then(async () => {
6 return value * 2;
7 })
However, if you start to view it from a certain outside context, it does not look that way anymore, and we need to understand it as something that will be executed in a different timeframe than the current top-down reading context:
1const myAsyncMather = async (a: number, b: number) => {
2 const value = a + b;
3
4 return await sleep(1000)
5 .then(async () => {
6 return value * 2;
7 })
8}
A Note on Threads
This article has largely focused on abstract concurrency and has only seldomly discussed threads—let alone process, green threads, coroutines, etc. For a given programming language and framework, it is important to know how the respective concurrent paradigms relate.
Ultimately, the thread is a
sequence of instructions. How a high-level programming language’s operators and
expressions map to machine instructions that runs in a thread varies. As an example, Java
historically correlated its instructions fairly closely to machine instructions. Often, a
reader could assume local code will all run on the same thread and would have
exclusive use of that thread, baring an obvious indicator otherwise such as
. Concurrency was often managed by executing asynchronous tasks on entirely new threads (or
at least on thread pools where each task would get complete ownership of a thread until it
was finished).new Thread()
Other runtimes such as JavaScript’s are quite different. JavaScript’s runtime
will generally have a single thread with the expectation that independent units of
instructions will yield the thread as fast as possible, e.g. by the use of the
operator, which gives the runtime’s event loop the opportunity to schedule another task.await