0% found this document useful (0 votes)
344 views39 pages

Could Virtual Threads Cast Away The Usage of Kotlin Coroutines

How do Virtual Threads can potentially affect the development of resilient services? If you are implementing services in the JVM, odds are that you are using the Spring Framework. As the development of possibilities for the JVM continues, Spring is constantly evolving with it. This presentation for the London Java Community (LJC) was created to spark that discussion and makes us reflect about out available options so that we can do our best to make the best decisions going forward.
Copyright
© Attribution ShareAlike (BY-SA)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
344 views39 pages

Could Virtual Threads Cast Away The Usage of Kotlin Coroutines

How do Virtual Threads can potentially affect the development of resilient services? If you are implementing services in the JVM, odds are that you are using the Spring Framework. As the development of possibilities for the JVM continues, Spring is constantly evolving with it. This presentation for the London Java Community (LJC) was created to spark that discussion and makes us reflect about out available options so that we can do our best to make the best decisions going forward.
Copyright
© Attribution ShareAlike (BY-SA)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 39

Could Virtual Threads cast away the

usage of Kotlin Coroutines? What


experience can tell us…

How coroutines have changed the JVM programming


world and how Java Virtual Threads can change that?

By João Esperancinha 2024/09/10


https://www.eventbrite.co.uk/e/ljc-meet-up-tickets-988279208717
Who am I?
João is a 10+ years experienced Software Developer,
Studied Telecom Engineering at ISEL Lisboa, and worked
in different companies in Portugal, Spain, and The
Netherlands. He is a certified Kong Champion, a
certified Java Developer (OCP11) and is a Certified
Spring Professional 2021. Outside the software
development Universe, João is someone interested in arts
and crafts and making stuff out of old materials creating
the occasional modified furniture. João loves plants and
has a small potted garden with Yuccas and blueberry
bushes. He has one particular YouTube channel called
JESPROTECH dedicated to engineering where João
shares his experience with the world.
Quick Threads History

1995 - Java 1.0 and Green Threads

1998 - Java 1.2 and Transition to Native Threads

2004 - Java 5 (1.5) and Concurrency Utilities


2011 - Kotlin Language Announced

2014 - Project Loom Announced


2017 - Kotlin 1.1 and Introduction of Coroutines

2019 - Project Loom Early Access Builds

2022 - Java 19 and Preview of Virtual Threads

2023 - Java 21 and Official Release of Virtual


Threads
Fetching data

Fetch all data diagram (Horrendous old way to fetch data for Server Side, but great
for mobile … sometimes)
Fetching data with Java Virtual
Threads (Java)
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var userDeferred = scope.fork(() -> fetchUser(userId));
var postsDeferred = scope.fork(() -> fetchUserPosts(userId));
var commentsDeferred = scope.fork(() -> fetchUserComments(userId));
scope.join();
scope.throwIfFailed();
var processedData = processUserData(
userDeferred.get(), postsDeferred.get(), commentsDeferred.get());
updateUI(processedData);
logger.info(() -> "Complete!");
} catch (ExecutionException e) {
Highly
throw new RuntimeException(e);
} catch (InterruptedException e) { verbose!
throw new RuntimeException(e);
} This is blocking! Alternative:
Thread.startVirtualThread ?

Structured Concurrency using Virtual Threads


Fetching data with Java Virtual
Threads (Kotlin)
ShutdownOnFailure().use { scope ->
val userDeferred = scope.fork { fetchUser(userId) }
val postsDeferred = scope.fork { fetchUserPosts(userId) }
val commentsDeferred = scope.fork {
fetchUserComments(
userId Still
) Highly
} verbose!
scope.join()
scope.throwIfFailed()
val processedData = processUserData(
userDeferred.get(), postsDeferred.get(), commentsDeferred.get()
)
updateSytem(processedData)
logger.info("Complete!")
} Still blocking! Alternative:
Thread.startVirtualThread ?

Structured Concurrency using Virtual Threads


Fetching data

CoroutineScope(IO).launch {
val userDeferred = async { fetchUser(userId) }
val postsDeferred = async { fetchUserPosts(userId) }
val commentsDeferred = async { fetchUserComments(userId) } Is this not
val user = userDeferred.await() verbose? 🤔
val posts = postsDeferred.await() It is
val comments = commentsDeferred.await() better! 😀
val processedData = processUserData(user, posts, comments)
updateSytem(processedData)
logger.info("Complete!")
}

Not blocking! 😀

Structured Concurrency using Kotlin Coroutines


Sending messages

Send messages diagram (N user messages, packed in 50 messages sent in parallel - Fire
and forget)
Sending Messages

suspend fun sendMessage(text: String, users: List<User>) = coroutineScope {


users.chunked(50).map {
it.map {

async {
retry(5, 500) { Needs a
sendEmail(text, it) coroutine
}
scope!
}
}.awaitAll()
}
}

Fire and Forget with Kotlin Coroutines


Sending Messages

fun sendMessage(text: String, users: List<User>) = users.chunked(50).map {


it.map {
startVirtualThread {
retry(5, 500) { No need for a
sendEmail(text, it) coroutine
} scope… but
} now we wait?🤔
}.forEach { it.join() } Now it is just a
}
choice!
😀
Example:
Thread.startVirtualThread

Fire and Forget with Java Virtual Threads


Take a
part of
1. services: the
2. fulfilment: machine
Making tests 3.
4.
build:
context: .
Using docker- 5.
6.
ports:
- "8080:8080"

compose 7.
8.
expose:
- 8080
9. deploy:
10. resources:
locust -f locust_fulfilment.py -- 11. limits:
host=http://localhost:8080 12. cpus: 0.5
13. memory: 270M
Python code to make tests
from locust import HttpUser, TaskSet, task, between

class UserBehavior(TaskSet):
URL
+
@task
def get_fulfilment(self): Header
url = "/api/v1/fulfilment"
params = {} s
headers = {"Accept": "application/json"}

with self.client.get(url, params=params, headers=headers, catch_response=True) as response:


if response.status_code == 200:
response.success()
print(f"Status Code: {response.status_code}, Response: {response.text[:100]}")
else:
response.failure(f"Failed with status code: {response.status_code}")

class WebsiteUser(HttpUser):
tasks = [UserBehavior]
wait_time = between(1, 3)

def on_start(self):
pass

def on_stop(self):
pass

Python code called by locust to perform tests


Traditional Spring MVC

@RestController
@RequestMapping("fulfilment")
This was
class FulfilmentController {
the old
@GetMapping way.
fun getItems() = (1..10).map { Or is it?
val product = Product(name = "TV", isleType = Room) 🤔
logger.info("Product: $product")
product.name
}

companion object {
val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java)
}
}
Locust test - Max 100000 users ; Spawn Rate 1000 users/second (~20 seconds = 20000+
simultaneously - MVC)
● Easy pattern to learn
● Good 3 tier model
● Model, View, Controller
● Domain model

Traditional
● Data transfer object
● Converters

Spring MVC Problem?

Great pattern, but not that ● Not many were aware of the inefficient aspect
of it
capable. Not “fast” enough. ● The inefficiency only appeared when raising
the load

Why was there a need to improve it?


June 2003
● The idea was there all along
● When comparing the performance of Spring for
higher loads, Spring and other JVM based
solutions would fail to be on top.
Reactive Services in Spring MVC -
WebFlux
@RestController
@RequestMapping("fulfilment")
Flux starts
class FulfilmentController {
reactive
@GetMapping revolution, but
fun getItems(): Flux<String> = Flux.fromIterable (1..10).map { how good
val product = Product(name = "TV", isleType = Room) was/is it?
logger.info("Product: $product")
product.name
}

companion object {
val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java)
}
}
Locust test - Max 100000 users ; Spawn Rate 1000 users/second (27 seconds = 27000 +
simultaneously - WebFlux)
● Blockhound

Reactive ●

Flux / Mono
Flux were used for collections

Programming
● Mono used for instances
● Good support for R2DBC
● Good support for Spring
● The Observability pattern and publishers were
visible

Problem?

Spring WebFlux - 2017 ● Coding would get extremely complicated


(my sea-shell-archiver ● Particularly zipping functions

became too complicated!)_


Reactive Services with Spring Kotlin
Coroutines
@RestController
@RequestMapping("fulfilment")
A flow is
class FulfilmentController {
programmed
@GetMapping like flux but
fun getItems() = flowOf(1..10).map { the backbone
val product = Product(name = "TV", isleType = Room) is made with
logger.info("Product: $product") Kotlin
product.name Coroutines
} and it works
with the
companion object { assigned
val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java)
dispatcher by
} Spring
}
Locust test - Max 100000 users ; Spawn Rate 1000 users/second
● Scope creation instead of a
specific structured concurrency
Why Kotlin block

Coroutines? ● Structured concurrency


● Non-preemptive scheduling
● Cooperative multitasking
● Better usage of resources
● Concurrency
Kotlin’s inception year - ● Additional benefit of
2016 parallelism
● Dispatchers
● Claimed to be simpler than using Flux and
Mono
● Instead of Mono we started using suspend
● Instead collections, we started using flow

Kotlin ●


The rise of the usage of Channels, hot flows,
cold flows
Talks about removing Kafka, RabbitMQ and

Coroutines major dedicated data stream frameworks

Problem?

● Coroutine complexity was still considerable


when using structured concurrency
● Because of the non-preemptive model all
Compatibility with Spring - functions and its instances would have to be
marked as suspend
2019 ● No simple function could call suspend
● Many developers still try to use suspend with a
returned list or even with flows. In some cases
also with Flux.
Coroutines to Virtual Threads (gross simplification)
Virtual Threads in Spring

# Server
server.port=8080
spring.main.web-application-type=servlet We can
spring.mvc.servlet.path=/api/v1
activate
# Enable Virtual Threads for WebFlux
virtual threads
spring.threads.virtual.enabled=true
# Thread pool configuration
#spring.task.execution.pool.max-size=16
#spring.task.execution.pool.queue-capacity=100
#spring.task.execution.pool.keep-alive=10s
Virtual Threads in Spring

@RestController
@RequestMapping("fulfilment") It’s back!
class FulfilmentController {
And now
@GetMapping
we are
fun getItems() = (1..10).map { back to
val product = Product(name = "TV", isleType = Room) the old
logger.info("Product: $product") model!😁
product.name
}

companion object {
val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java)
}
}
Locust test - Max 100000 users ; Spawn Rate 1000 users/second
● Turns everything on its head for Spring
● There is no keyword or special objects needed
● The application stays reactive
● The preemptive/non-preemptive (hybrid)
scheduling form makes sure that we can
virtualize threads in a code that goes back to

Virtual ●
the imperative form, without the need to be
forced to make major decisions
The use of scopes is off the table and not

Threads needed

Problem?

● Structured concurrency support is very


verbose
Started with Loom as ● Many frameworks surrounding Kotlin like
Project arrow rely on coroutines for their
Fibers in 2017 features.
Released in 2023 with the ● Streams, channels and flows offer full
coroutine support, while it is still cumbersome
JDK 21 to do that in Java
● Creating mobile applications, namely with
android, uses a lot of coroutines
Coroutines with Virtual Threads (gross simplification)
Coroutines on Virtual Threads in
Spring
@RestController
@RequestMapping("fulfilment")
class FulfilmentController {

@GetMapping
fun getItems(): Flow<String> = (1..10).asFlow().map {
An odd
val product = Product(name = "TV", isleType = Room) constructio
logger.info("Product: $product") n, but does
product.name it stand?🤔
}.flowOn(
Executors.newVirtualThreadPerTaskExecutor()
.asCoroutineDispatcher()
)

companion object {
val logger: Logger = LoggerFactory.getLogger(FulfilmentController::class.java)
}
}
Locust test - Max 100000 users ; Spawn Rate 1000 users/second
Virtual Threads
● A different perspective on Threads with
Combined potential benefits
● I never used it in production
With Kotlin ●

Maybe it is not an improvement.
Again, testing is needed and lot of
Coroutines benchmarking is needed in order to make
adequate decisions

2023 lot’s of blogs


released:
I did that too:
https://github.com/
jesperancinha/good-story
GitHub - Good Story - Virtual Thread Dispatcher in combination with Kotlin Coroutines
Conclusion
● Virtual threads are very impactful because of their support by the Spring Framework. They are
easy to use and build code with. Maintain that code is easy. It can be used in:
○ Fire and forget scenarios
○ Structured concurrency
○ Both Java and Kotlin
● Coroutines will still be used because of how well and elegantly they adapt to other paradigms:
○ Flows
○ Channels
○ Event Streaming
○ SSE
○ Can be used in Both Java and Kotlin, but they are not prepared for Java and can get
highly complicated that way
● In any case, both can be used with similar or purely equal performance markers, leading to
choosing them on the bases of code style, patterns and design.

Virtual Theads and Coroutines have a great future still


What next?

➔ Java Virtual Threads have a definite future, but for Android at the moment, only up
to JDK17 is supported and no support is available for Java Virtual Threads as a
result.

➔ Kotlin Coroutines will stick around for Android for a long time.

➔ In Java Server Side the use of Kotlin Coroutines may go down for simple
applications and only where certain specific operations are needed will Kotlin
coroutines still likely be needed due to their simplicity.

➔ For all operations related to structured concurrency, streams and event handling,
the usage of Kotlin Coroutines may actually increase as is the case with Mobile
Applications in Android.

➔ Maybe coroutines can one day not be used anymore given the potential of Java
Virtual Threads even in the case of Android applications, but that isn’t anything
Questions?

I am an
inquisitive cat
Resources

Online

● https://kotlinlang.org/docs/coroutines-basics.html
● https://openjdk.org/jeps/444
● https://openjdk.org/projects/jdk/21/
● https://www.jetbrains.com/help/idea/supported-java-versions.html
● https://discuss.kotlinlang.org/t/coroutines-java-virtual-threads-and-scoped-values/28004/1
● https://developer.android.com/build/jdks

Slides available:

● https://www.scribd.com/presentation/768072685/Could-Virtual-Threads-Cast-Away-the-Usage-
of-Kotlin-Coroutines
● https://www.slideshare.net/slideshow/could-virtual-threads-cast-away-the-usage-of-kotlin-coro
utines/271727680
Source code and documentation

● https://github.com/jesperancinha/virtual-thread-coroutine-cooperation (Source code)

● https://github.com/jesperancinha/good-story (CPU bound operations comparison)

● https://github.com/jesperancinha/sea-shell-archiver (Coding with WebFlux - Mono/Flux/Zip)

● https://github.com/jesperancinha/concert-demos-root (Back Pressure testing in Spring WebFlux


and R2DBC)
● https://github.com/jesperancinha/kitten-house-care-parent (Basics on resilient services)
About me

● Homepage - https://joaofilipesabinoesperancinha.nl
● LinkedIn - https://www.linkedin.com/in/joaoesperancinha/
● YouTube - JESPROTECH
■ https://www.youtube.com/channel/UCzS_JK7QsZ7ZH-zTc5kBX_g
■ https://www.youtube.com/@jesprotech
● Bluesky - https://bsky.app/profile/jesperancinha.bsky.social
● Mastodon - https://masto.ai/@jesperancinha
● GitHub - https://github.com/jesperancinha
● Hackernoon - https://hackernoon.com/u/jesperancinha
● DevTO - https://dev.to/jofisaes
● Medium - https://medium.com/@jofisaes

You might also like