
This course includes our updated coding exercises so you can practice your skills as you learn.
See a demo
Explore Java multi-dwelling and parallel programming to boost application performance by leveraging CPU power, with hands-on practice, visual basics, and real-world industry connections.
Maximize value by aligning your approach with a practical, end to end learning journey in parallel programming through hands-on and visual lectures, projects, and code analysis.
Identify course prerequisites, choose a Java version and IDE, and access the GitHub repository organized by chapter and lecture to run the coding examples.
Explore how a process, an in-memory instance of a program, moves through new, ready, running, waiting, and terminated states under operating system’s scheduler, with preemption, time slices, and context switches.
Explore how a thread manages a stream of instructions and runs alongside the main thread. Compare threads and processes, examine join blocks, and address synchronization of shared data.
Explain how processes run at operating system level with isolated address spaces, while threads run within the Java runtime for tasks; use a thread pool to scale and consider overhead.
Clarify concurrency, parallelism, asynchronous, and non-blocking concepts and show how independent tasks can run in any order or in parallel, with dependencies shaping program output.
Explore Amdahl's law and how speed up depends on the parallelizable fraction. See how 50 percent parallelism yields about 1.33x speed up and how the serial portion limits gains.
Explore thread creation in Java, including extending Thread versus implementing Runnable, using Thread.currentThread and thread name, sleep and interruption handling, and proper start, join, and lambda-based Runnable for concurrency.
Master threading with thread groups, learn to start and join multiple threads, set max priority, perform interrupts, and understand parent-child relationships in the thread hierarchy.
Examine daemon threads and user threads in Java, showing how the JVM exits after user threads finish while daemon threads may terminate early.
Explore how Java handles checked and unchecked exceptions in multithreaded code, including try-catch, throws, and uncaught exception handlers across threads and thread groups.
Explore thread local variables and shared class-level fields, and how race conditions arise in a global counter across multiple threads.
Watch a directory for new files and spawn a thread per file to enable parallel processing. Hash every line and save outputs to an output directory with an underscore suffix.
Learn how thread synchronization prevents race conditions by ensuring only one thread executes a critical section at a time, using mutual exclusion for shared variables in Java.
Explore how the synchronized keyword fixes race conditions by guarding a shared counter with a single thread critical section, using a monitor object and optional method or class level synchronization.
Explore how wait sets and notifications coordinate producer and consumer threads in Java, using wait, notify, and notify all within synchronized blocks to manage a 10-element queue.
Explore how locks provide synchronized access to critical sections in Java, compare them with synchronized blocks, and use try lock and unlock to safely parallelize a vector sum.
Explore read-write locks and spin locks in Java multithreading, using a reentrant read-write lock to allow concurrent readers and exclusive writers, with a spin lock example illustrating busy-wait behavior.
Explore Java condition variables for producer–consumer synchronization, creating multiple conditions from a single lock, using wait, signal, and critical sections to coordinate threads.
Learn how Java semaphore controls concurrency by acquiring and releasing permits, blocking and non-blocking, and limiting concurrent tasks with examples of an executor that caps active jobs.
Explore atomic variables in Java's concurrency toolkit, focusing on AtomicInteger; perform thread-safe increments via increment and get, set, and compare-and-swap, enabled by non-blocking hardware CAS.
Learn how to use countdown latch to coordinate parallel array search in Java, with a one-shot counter, await until all threads complete, and then examine the found position.
Explore Java cyclic barrier synchronization using await to coordinate multiple threads, reuse the barrier after release, and execute a runnable on release in a matrix-style, one thread per column example.
Learn how phasers provide flexible barrier synchronization in Java, compare them with acyclic barriers, and use register, arrive, and deregister to coordinate a two-phase parallel array doubling and summing.
Exchanger enables two threads to swap data using a generic exchange method, acting like a barrier, requiring a matching partner to avoid deadlock.
Explore how deadlocks arise when threads acquire locks in inconsistent order and block progress. Use consistent lock ordering or tryLock with timeout to prevent deadlocks and handle livelocks.
Explore how the volatile keyword ensures visibility of a shared variable across threads by forcing reads and writes to main memory.
Explore a simulated map reduce job for big data processing, detailing map, shuffle, and reduce steps, intermediate key-value formats, and a word count example using thread-based parallelism.
Implement parallel mapper, partitioner, and reducer threads to simulate a mapreduce job, using input splits, synchronized intermediate results, and a countdown latch to coordinate.
Learn how reusing a limited pool of long-running threads with a task queue reduces memory use and context switching, preventing out-of-memory errors while efficiently processing many files.
Explore java's thread pool executor, including core and maximum pool sizes, a blocking queue that expands with load, runnable and callable tasks, futures, and shutdown with await termination.
Explore blocking queues for thread pools, including array blocking queue (bounded), linked blocking queue (unbounded), and the synchronous queue, and learn how queue capacity shapes task rejection.
Understand how thread pools handle unchecked exceptions from tasks, including random exceptions and catchall, with futures and get, and custom executors.
Learn how a thread pool rejects tasks when the pool and queue are full, and how a RejectedExecutionHandler changes behavior, enabling caller runs policy, discard, or retry instead of exceptions.
Monitor a thread pool in production using four metrics: get forces size, get active count, get task count, and get completed task count to gauge utilization and throughput.
Learn how the scheduled thread pool executor runs tasks after delays or at fixed rates, extending the thread pool executor and managing delayed tasks and cancellation policies.
Explore how the ForkJoinPool executes divide-and-conquer tasks using work-stealing, common pool behavior, and recursive actions and tasks, demonstrated on a parallel array increment.
Discover how to create thread pools in one line using the Java Executors utility, including fixed, cached, scheduled, single-thread, and work-stealing pools, with a customizable thread factory.
Size a thread pool by cores and workload type, from CPU-heavy to IO-heavy, and apply the one-plus wait-time over service-time rule to maximize throughput.
Explore how quicksort solves sorting by partitioning around pivots and using parallelism with fork-join tasks to process subarrays concurrently, improving performance on large arrays.
Analyze binary search on a sorted array using divide and conquer and evaluate multithreading. Conclude that binary search is already optimal and not parallelizable.
Explore how to implement parallel matrix multiplication in java by launching a task for each output element, compare the serial and parallel versions, and observe a 2x speedup.
apply block matrix multiplication to parallelize matrix multiplication using eight multiplication tasks and four addition tasks across blocks, synchronized by semaphores and intermediate result matrices.
This lecture explains the dining philosophers synchronization problem, demonstrates deadlock when all grab forks, and solves it by using a semaphore to limit concurrency.
Explore the readers-writers problem, balancing multiple readers and a single writer in a critical section with locks and a binary semaphore to ensure data coherence.
Explore the sleeping barber problem by modeling the barber as a long-lived thread and customers as short-lived threads, using locks and semaphores to synchronize the waiting room.
Explore the no-starve mutex solution with two rooms and semaphores to guard the critical section, and examine Java fairness and weak versus strong locks.
Explore how JVM profiles reveal what the runtime does, focusing on threads, garbage collection, and performance, using VisualVM to monitor real-time thread states and stack traces.
Discover Spring Boot, a JVM-based framework that accelerates enterprise web apps with annotations, embedded Tomcat, and YAML-based configuration, while showing REST APIs and asynchronous offloading of blocking work.
Enable async in spring boot and delegate heavy workloads to a dedicated thread pool, using an asynchronous service method that returns a CompletableFuture<Boolean> and is autowired into the controller.
Explore reactive programming in Java through RxJava, modeling apps as event chains with observables and observers, using map operators and back pressure to control event streams.
Build parallel data pipelines in Java with RxJava2 by creating Flowable pipelines, applying map and filter, and using parallel and sequential operators with schedulers to run tasks in parallel.
learn how to unblock the UI in JavaFX by running heavy tasks on a background thread using the task class and executor service, updating the UI on completion.
Discover how java virtual threads suspend blocking io operations from platform threads, boosting throughput for io-bound workloads while not improving cpu-bound tasks.
Explore integrating virtual threads into a Spring Boot app to handle IO and external API calls with synchronous-looking code, and compare with traditional async patterns using CompletableFuture.
Discover structured concurrency in the Java 21 preview, using a try-with-resources task scope with forked subtasks and join to cancel remaining tasks on failure or interruption.
Intro
This course is the best online resource you need to become proficient in working with threads and correctly apply Multithreading techniques to your applications, in order to leverage the CPU capabilities of your machines and max out the application throughput.
The goal of this course is to make you deeply understand the multithreading concepts (that can be re-used in many other languages), applied and exemplified in Java, the language used by many large companies and more than 9 million developers around the world.
About myself
I wrote my first line of code 10 years ago when I was in highschool. I quickly got addicted by how easy you could build useful programs using C# and Windows Forms.
I followed the Computer Science University track where I managed to set the ground knowledge for anything related to Software Engineering (Algorithms, Data Structures, Operating Systems, Multithreading, Distributed Computing, Networking, and many other topics), and I finalized this amazing 6-year learning path by getting a Master's Degree in Parallel and Distributed Systems where I built from the ground a custom Kubernetes Gang Scheduler optimised for running Spark Jobs.
Currently, I'm a Software Engineer focused on high-scale JVM-based development. I build code used by millions of people around the world.
Why I built this course?
Multithreading is an advanced topic for any developer. I saw many people struggling to understand things like:
How can I speed up the runtime of this code?
Is it possible to split this problem into multiple independent pieces?
How can I measure the performance of this code?
Why is my multithreaded code stuck? How can I debug it?
It was really hard for me too to understand some of those things, even if I had enough university background in this area.
But fortunately, after many years of working with threads, many trial and errors, many profiling sessions and books & articles read, I managed to deeply understand those critical concepts and use them properly in my daily job.
For those reasons, I thought that building a course where I expose my understanding on Multithreading, would definitely help other people to save time and avoid going into the same pitfalls that I went through.
This course is going to be continuously updated with new information in the multithreading field, but also with the relevant topics that you request in the Q&A section, so you're buying a true learning asset, since you can use this course as a technical reference.
What is the content course?
This course is split into multiple chapters, each one exposing a major topic in Multithreading:
Chapter 1 - General Multithreading Concepts
In this chapter we're going to learn the basics of Multithreading - threads, processes, concurrency, parallelism. This chapter is full of visual lectures, designed to provide a unique learning experience.
Chapter 2 - Thread Management
This is the first hands-on chapter where we're going to learn how to create threads, how to use thread groups, daemon threads, but also how to build exception control flows, to avoid crashes due to unhandled exceptions.
Chapter 3 - Thread Synchronization - Part 1
This chapter goes into the main challenge when working with multiple threads, which is thread synchronization so that we get a predictable output of our application and avoid inconsistent behaviours.
We're going to learn basic synchonization tools - locks, wait sets and notifications, read & write locks and semaphores
Chapter 4 - Thread Synchronization - Part 2
This chapter is a continuation of the previous one, where we're going to tackle advanced synchronization tools, like Barriers and Phasers, but we're also going to learn about deadlocks and cache coherency enforcement by the use of the volatile keyword.
Chapter 5 - Thread Reusability
We can't create an infinite number of threads in our applications, because each thread needs some resources in order to be created, so for that reason we need to reuse threads.
This chapter describes the tools we have in Java to deal with thread reusability (Thread Pools) and it goes deep into how to work with them, manage performance, choose the right parameters (tuning) and many others.
Chapter 6 - Parallel Algorithms
In this chapter we're going to see how can we improve the runtime of a couple of known algorithms through multithreading.
We're going to learn the thinking process of breaking a problem into multiple pieces which can be processed in parallel, and finally merging the results to get the main output.
Chapter 7 - Famous Multithreading Problems
The first steps in Multithreading have been done many years ago, where famous computer scientists have tackled the problems which are know part of the Java Threading API.
In this chapter, we're going to study a couple of those problems and get the thought process of their solution. This exercise is very valuable and contributes to the overall understanding of parallelism and synchronization.
Chapter 8 - Multithreading in Real World
This final chapter of this course tackles the connection between Multithreading and widely used frameworks, like Spring-Boot, JavaRx and JavaFX. We're going to see how can we design a REST API in Spring Boot, which processes requests in an asynchronous way, leveraging multithreading.
We're going to see how can we build parallel data flows with JavaRx2, and also how to decouple the UI updates from the background processing in JavaFx, which technically applies to mobile and desktop applications.
What are the requirements for this course?
Basic Java Knowledge (including Object Orientated Programming)
An IDE of your choice, ideally IntelliJ Idea Community Edition, but you can use any IDE where you can run plain Java code
Willingness to learn and an open-mind
Thank you for taking the time to look through this description and I'm looking forward to see you in the first lecture!