
Explore what a thread is, why we need thread synchronization, and the Java mechanisms for synchronization, and learn to use them correctly to write professional multi-threaded software in Java.
Explore how an application becomes a process with an executable thread, how the JVM loads classes, starts the main thread, and creates additional threads to speed up multi-threaded Java programs.
Create a simple sequential Java application that calculates total sales for a given period using an array of daily sales, start and end arguments, and a calculateTotals method.
Explore how to create and run a new thread in Java using Runnable, access the main and new threads, and name or identify threads by id for parallel execution.
Practice for this video
Please check what happens if you:
1. Call the start method two times
2. Call the join method before the start method
3. Set the daemon flag after starting a thread
4. Call the join method for a daemon thread
Also, please read the documentation for class Runnable and methods
start, join and setDaemon of the class Thread
Explore how thread synchronization prevents data races in Java by using the synchronized keyword, monitors, and careful lock granularity to coordinate multiple threads and ensure atomic updates.
Understand how volatile variables enable atomic reads and writes and uphold happens-before visibility across threads, supporting flags and shared references. Note increments are not atomic and its use is lightweight.
Explore atomic variables that replace volatile counters with lock-free, thread-safe operations. Learn atomic types and methods like increment and get, get and increment, set, and compare and set, ABA problem.
Practice for this video
Find the throughput of your system, for that:
1. Add some mathematical work to the method process instead of the Thread.sleep call
2. Add a global counter which will count, how many items were processed (it can be an atomic variable)
3. Find the optimal number of producer and consumer threads
4. Find the best throughput of your system
Discover how the thread local class creates per-thread variables read and written by the same thread, avoiding synchronization. Explore initialization options and inheritable thread locals for child threads.
Master Java concurrency concepts, including thread creation, join, interrupt, and daemon threads. Learn synchronization with the synchronized keyword, data races, volatile and atomic variables, wait/notify, producer-consumer patterns, and thread-local data.
Demonstrates data race: two threads increment a shared counter without synchronization, producing unpredictable results; introduce a shared lock and synchronized access to ensure correct values under heavy load.
Demonstrates race condition in a concurrent java environment and uses a volatile flag, a synchronized block, and a double-check to avoid loading already loaded data.
Prevent deadlock by ensuring threads acquire locks in the same order. Enforce a consistent lock order using clearly named locks and suffixes, and inspect thread dumps to diagnose.
Starvation occurs when a thread cannot regularly access a shared resource due to long synchronized calls causing lock contention, with profiling from Java Mission Control identifying blocked threads.
Master multithreading by splitting work across threads and cores for higher throughput and responsiveness, using synchronized, volatile, and atomic constructs and wait/notify to avoid data races and livelock.
Explore the java.util.concurrent package, including locks, semaphores, countdown latch, cyclic barrier, exchanger, and synchronized collections, plus executors and the completable feature for scalable, concurrent applications.
Explore how to use java.util.concurrent locks, including lock, unlock, tryLock, and lockInterruptible, with a re-entrant lock implementation and fairness considerations.
This lecture explains how a semaphore coordinates access to limited resources by tracking permits, such as processor cores, using acquire and release, with fairness and optional try or drain methods.
Learn how to use the cyclic barrier to parallelize a sum and percent calculations, splitting work, synchronizing threads, and reusing the barrier for stages.
Practice for this video
Reimplement the merge-sort algorithm on your own using the CountDownLatch synchronization primitive
1. Create a queue for sorting tasks
2. Create a producer thread that publishes sorting tasks
3. Create consumer threads that take tasks from the queue and do sort
4. Create a method that waits for a competition of all sorting tasks and then does a merging stage
5. Add a test that checks that data are sorted correctly
Explore how the exchanger provides a bidirectional synchronization point for pairing threads to swap tasks and compute sums in parallel.
Explore the phaser, a flexible Java concurrency primitive that replaces cyclic barrier and countdown latch by dynamic registration, multi-phase coordination, and synchronized sum and percentile calculations.
Demonstrates copy-on-write collections, including copy-on-write array list and copy-on-write array set, showing safe reads with internal synchronization, and expensive updates with unsupported iterator remove.
Practice for this video
Implement your own capacity restricted queue:
1. With blocking methods put and take (it's not necessary to implement other methods)
2. Replace with it the LinkedBlockingQueue in our example
3. Write a test that checks that your queue works correctly with several producers and consumers
You can use the synchronized keyword and wait/notify calls or ReentrantLock and Condition variable
Examine the java.util concurrent blocking queues, including array, linked, priority, delay, synchronous, transfer, and linked transfer queues. Learn how these thread-safe structures enable various blocking and throughput patterns.
Explore the concurrent map interface and its java.util.concurrent implementations, including the concurrent hash map, to enable thread-safe, atomic updates for subscription contexts without external synchronization.
Practice for this video
You have to make refactoring of code from the video and create a MapReduce framework. For that
1. Create a MapReduce class
2. Parameters of the constructor should be:
text file (or path to the file)
map function
reduce function
3. Add a method execute
4. This method should return a collection of reduced Keys and Values
5. The type of Keys and Values should be generic. The Key can be Integer for example, and Value – Double.
6. Prepare a text file with several lines with numbers
7. Calculate the sum of values that are:
less than 100 (first key)
between 100-1000 (second key)
greater than 1000 (third key)
Explore the java.util.concurrent collections, including synchronized wrappers, copy-on-write structures, blocking and non-blocking queues, and concurrent maps and navigable skip-list implementations, with guidance on safe iteration and high-throughput design.
Explore the executor service in java.util.concurrent, learn to replace direct thread management with fixed, single, cached, and scheduled pools, and work with runnable and callable tasks and futures and shutdown.
Explore scheduling tasks after a delay or periodically with Java timer and the scheduled thread pool executor, covering timer task, daemon flag, cancellation, and future.
Learn to configure thread pool execution with ThreadPoolExecutor, including core and max pool sizes, keep-alive time, work queues, custom thread factories, and uncaught exception handling.
Thread pools from the java.util concurrent package simplify thread management, provide futures for asynchronous results, support scheduled execution, and fork-join pools for recursive number-crunching, with care to shutdown resources.
Explore the Java Stream API and parallel streams to process collections, map, filter, and collect, then compute max, min, and count with optional results.
Explore spin locks and busy locks for high-performance multi-threaded Java, using volatile and compare-and-set, and learn when these techniques suit your project or are better avoided.
Learn about lock-free, non-blocking algorithms by implementing a thread-safe queue using atomic references and compare and set operations. Understand progress guarantees, and distinguish lock-free from wait-free designs.
Explore how throughput and latency shape performance in multi-threaded applications, using a producer-consumer example, atomic counters, and percentile-based latency measurements.
Learn to monitor Java applications in real time using JMX, JConsole, Java Mission Control, and SJK to track throughput, latency, memory, and allocation rate while tuning parameters like queue size.
Explore profiling to locate hot points and garbage sources, using Java Mission Control flight recorder to sample thread activity, monitor allocations, memory, and GC pauses, and identify hot methods.
Learn to measure Java method performance with JMH, using benchmarks, warm-ups, forks, and parameters to report throughput and nanosecond timing while avoiding JVM pitfalls.
Explore testing in a java concurrency context, from unit tests for single methods to thread safety verification with barriers and latches, then stress and performance testing for real-world load.
In this course, we will discuss Java Concurrent and Multithreaded Programming in detail. The course covers basic topics such as threads creation, synchronization, memory model and a happens-before relationship. There are lectures about standard Java classes that help to write complex concurrent programs. Also, there are advanced sections about concurrent algorithms, performance and monitoring.