Java Concurrency - Loom Edition Course

Chania, Crete, 26-30 September 2022

  Enroll in Course

Overview

Java was built to be able to do many things at once. In computer lingo, we call this "concurrency". This is the main reason why Java is so useful.

Java programmers need to master concurrency. If they do not, system might break on the busiest shopping day. Concurrency bugs tend to appear when we do a lot of things at the same time, such as dealing with many clients.

During this course, you will learn how to write safe multi-threaded Java code that performs well on your hardware. You will learn techniques to ensure visibility of your fields, to build thread safety without explicit locking. You will learn the new fork/join constructs and explore ways to parallelize your algorithms.

Virtual threads (JEP 425) allow us to create millions of threads in a single JVM. This course covers virtual threads from start to finish, and shows coding techniques to take advantage of them.

Schedule

Dates: 26-30 September 2022

Times: Mon-Fri 9:00-12:00 and 13:30-16:00 Athens Time (Eastern European Daylight Savings Time)

Students can either come to Chania Crete to join us in person (limit of 7 students) or they can attend remotely via Zoom (limit 12 students).

For those coming to Crete, lunch is included in the price and is from 12:00 to 13:30. Tea times are variable, depending on how the course progresses.

Get started now!



Your Instructor


Dr Heinz M. Kabutz
Dr Heinz M. Kabutz

Heinz Kabutz is the author of The Java Specialists’ Newsletter, a publication enjoyed by tens of thousands of Java experts in over 150 countries. He has been a Java Champion since 2005.


Detailed Outline

  • 1 Introduction
    • Welcome To The Course
      • How we deal with questions
      • Exercises with partial solutions
      • Certificate of Training
    • 1.1 History of concurrency
      • Processes vs Threads vs Virtual Threads
      • Moore's Law and Transistor Count
      • Concurrent vs Parallel Programming
      • Preemptive vs Cooperative vs Virtual Multithreading
      • Java Memory Model
    • 1.2 Benefits of threads
      • Programming is easier
        • Concurrent Programming
        • Parallel Programming
      • Better throughput
      • Simpler modeling
      • More responsive programs
    • 1.3 Risks of threads
      • Safety vs liveness
      • Safety hazards
      • Using basic synchronized
      • Caching of fields
      • Code reordering
      • Annotations for Concurrency
        • Class annotations
        • Field annotations
    • 1.4 Threads are everywhere
      • Timer
      • Servlets and JavaServer Pages
      • Remote Method Invocation (RMI)
      • Swing and AWT
      • HttpClient in Java 11
    • 1.5 Short Java Migration Primer
      • Java 7: underscores, diamond, try-with-resource
      • Java 8: lambdas, streams, compound collection methods
      • Java 11: JPMS, immutable collections, var
      • Java 17: switch expressions, text blocks, records, pattern matching, strong\nencapsulation, sealed classes
      • Java 19-previes: virtual threads
  • 2 Thread Safety
    • Introduction to Thread Safety
      • Synchronization and shared data
      • Your program has latent defects
      • Object orientation and synchronization
      • Example of a simple data race
    • 2.1 What is thread safety?
      • Definition of thread safety
      • Stateless objects always thread-safe
    • 2.2 Atomicity
      • Byte code generated by simple count++
      • Demonstration of broken servlet
      • Lazy initialization check-then-act
      • Reordering of writes
      • Compound actions
        • Check-then-act
        • Read-write-modify
      • Using AtomicLong to keep state safe
      • Request counting with LongAdder
    • 2.3 Locking
      • Mutexes in Java
      • Atomics causing race conditions
      • Monitor locks
      • Effects of coarse-grained locking
      • Reentrancy
    • 2.4 Guarding state with locks
      • Serialized access to critical code
      • Examples of guards with compound actions
      • Making compound actions atomic
      • Synchronizing on non-final fields
      • Synchronizing on Value-Based Classes
      • Locking on monitor lock "this"
      • Maintaining invariants
      • Locking everything is not always enough
      • Synchronization on reads
      • Synchronized and Virtual Threads
    • 2.5 Liveness and performance
      • Coarse-grained vs fine-grained locking
      • Performance difference
      • Simplicity vs performance
  • 3 Sharing Objects
    • 3.1 Visibility
      • Synchronization and visibility
      • Reason why changes are not visible
      • Problems that state data can cause
      • Non-atomic 64-bit numeric operations
      • Making fields visible with volatile
      • Volatile flushing
      • Volatile vs synchronized
      • Single-threaded write safety
      • Volatile Doesn't Guard Against Race Conditions
    • 3.2 Publication and escape
      • Ways we might let object escape
      • Publishing objects via fields
      • Publishing objects as method returns
      • Publishing objects to alien methods
      • Implicit links to outer class
        • Lambdas and method references
      • Safe construction practices
    • 3.3 Thread confinement
      • Unshared objects are safe
      • Thread confinement
        • ThreadLocal in Virtual Threads
      • Stack confinement
      • ThreadLocal
      • Instance confinement
    • 3.4 Immutability
      • Immutable is always thread safe
      • Definition of immutable
      • Immutable containing mutable object
      • Final fields
    • 3.5 Safe publication
      • Making objects and their state visible
      • Safe publication idioms
      • "Effectively" immutable objects
      • How to share objects safely
        • Thread-confined
        • Shared read-only
        • Shared thread-safe
        • Guarded
  • 4 Composing Objects
    • 4.1 Designing a thread-safe class
      • Encapsulation
      • Primitive vs object fields
      • Thread-safe counter with invariant
      • Post-conditions
      • Pre-condition
      • Waiting for pre-condition to become true
    • 4.2 Instance confinement
      • Encapsulation
      • State guarded by private fields
      • Split locks
      • How instance confinement is good
      • Java monitor pattern
      • Lock confinement
      • Example of fleet management
    • 4.3 Delegating thread safety
      • Using thread safe components
      • Delegation with vehicle tracker
      • Delegating safety to ConcurrentMap
      • Independent fieldds
      • Invariables and delegation
        • Using AtomicLong for combining ints
      • Publishing underlying fields
    • 4.4 Adding functionality to existing thread-safe classes
      • Benefits of reuse
      • Modifying existing code
      • Subclassing to add functionality
      • Using composition to add fiunctionality
      • Client-side locking
    • 4.5 Documenting synchronization policies
      • Examples from the JDK
      • What should be documented
      • Synchronization policies
      • Documentation checklist
      • Interpreting vague documentation
  • 5 Building Blocks
    • 5.1 Synchronized collections
      • Old Java 1.0 thread-safe containers
      • Synchronized wrapper classes
      • Locking with compound actions
      • Adding functionality to thread-safe classes
      • Exceptions with iteration
      • Hidden iterators
      • Benefits and limitations with "synchronized"
    • 5.2 Concurrent collections
      • Scalability
      • ConcurrentHashMap
      • Additional atomic operations
      • ConcurrentSkipListMap
      • CopyOnWriteCollections
      • ConcurrentLinkedQueue
    • 5.3 Blocking queues and the producer-consumer pattern
      • How BlockingQueues work
      • Timed vs untimed methods
      • Java implementations of BlockingQueue
        • LinkedBlockingQueue
        • ArrayBlockingQueue
          • Circular array lists
        • PriorityBlockingQueue
        • DelayQueue
        • SynchronousQueue
        • TransferQueue
      • Example of producer-consumer
      • Deques
        • ArrayDeque
        • LinkedBlockingDeque
        • ConcurrentLinkedDeque (Java 7)
      • Work stealing
    • 5.4 Blocking and interruptible methods
      • Thread states during blocking
      • Interrupting blocking methods
      • Methods throwing InterruptedException
      • ManagedBlocker
      • Blocking Methods and Virtual Threads
    • 5.5 Synchronizers
      • Coordinating flow of threads
      • Semaphore
      • CountDownLatch
      • AbstractQueuedSynchronizer
      • CyclicBarrier
      • FutureTask
      • Phaser
  • 6 Task Execution
    • 6.1 Executing tasks in threads
      • Identifying tasks
      • Indepence of tasks
      • Task boundaries
      • Single-threaded vs multi-threaded
      • Explicitely creating tasks
      • Disadvantage of unbounded thread creation
      • Creating virtual threads
    • 6.2 Virtual Threads
      • Starting Virtual Threads
      • Virtual Thread Characteristics
      • Carrier Threads
      • Platform Threads State Machine
      • Virtual Threads State Machine
      • Thread.Builder
        • Thread.Builder.OfPlatform
        • Thread.Builder.OfVirtual
    • 6.3 The Executor framework
      • Executor interface
      • Motivation for using Executor
      • Executors Facade pools
      • Decoupling task submission from execution
      • Thread pool benefits
      • Virtual Thread Executor vs Pool
      • Submitting Callable
      • Executor lifecycle, state machine
        • Shutdown() vs ShutdownNow()
        • AutoCloseable (Project Loom)
      • Memory leaks with ThreadLocal
      • Delayed and periodic tasks
        • Difference between java.util.Timer and ScheduledExecutor
    • 6.4 Finding exploitable parallelism
      • Breaking up a single client request
      • Sequential vs parallel
      • Callable and Future
      • Callable controlling lifecycle
      • Example showing page renderer with future
      • Limitations of parallelizing heterogeneous tasks
      • CompletionService
      • Time limited tasks
      • CompletableFuture
        • Chaining of CompletionStages
        • Modeling control flow
        • Using separate pools for stages
        • HttpClient in Java 11
        • CompletableFuture Tips
      • Parallel Request with Virtual Threads
        • Structured Concurrency
        • Managing Error Conditions
        • Timeouts in Requests
    • 6.5 Using Parallel Streams
      • Processing in parallel
      • When to use / avoid
      • Common Fork/Join Pool
  • 7 Cancellation
    • 7.1 Task cancellation
      • Reasons for wanting to cancel a task
      • Cooperative vs preemptive cancellation
      • Using flags to signal cancellation
      • Cancellation policies
      • 7.1.1 Interruption
        • WAITING state of thread
        • Origins of interruptions
        • How does interrupt work?
        • Methods that put thread in WAITING state
        • Policies in dealing with InterruptedException
        • Thread.interrupted() method
        • Interruptions in Virtual Threads
      • 7.1.2 Interruption policies
        • Task vs Thread
        • Different meanings of interrupt
        • Preserving the interrupt status
      • 7.1.3 Responding to interruption
        • Letting the method throw the exception
        • Restoring the interrupt and exiting
        • Ignoring the interrupt status
        • Saving the interrupt for later
      • 7.1.4 Example: timed run
        • Telling a long run to eventually give up
        • Canceling busy jobs
      • 7.1.5 Dealing with non-interruptible blocking
        • Reactions of IO libraries to interrupts
        • Interrupting locks
    • 7.2 Stopping a thread-based service
      • Graceful shutdown
      • Providing lifecycle methods
      • Example: A logging service
      • Asynchronous logging caveats
      • ExecutorService shutdown
      • Poison pills
      • One-shot execution service
    • 7.3 Handling abnormal thread termination
      • Using UncaughtExceptionHandler
      • ThreadGroup for uncaught exceptions
      • Dealing with exceptions in Swing
    • 7.4 JVM shutdown
      • Orderly shutdown
      • Abrupt shutdown
      • Shutdown hooks
      • Daemon threads
  • 8 Applying Thread Pools
    • 8.1 Tasks and Execution Policies
      • Homogenous, independent and thread-agnostic tasks
      • Thread starvation deadlock
      • Long-running tasks
    • 8.2 Sizing thread pools
      • Danger of hardcoding worker number
      • Problems when pool is too large or small
      • Formula for calculating how many threads to use
      • CPU-intensiv vs IO-intensive task sizing
      • Examples of various pool sizes
      • Mixing different types of tasks
        • Platform vs Virtual Thread Pools
    • 8.3 Configuring ThreadPoolExecutor
      • corePoolSize, maximumPoolSize, keepAliveTime, workQueue
      • Using default Executors.new* methods
      • Managing queued tasks
      • PriorityBlockingQueue
      • Saturation policies
        • Abort
        • Caller runs
        • Discard
        • Discard oldest
      • Thread factories
    • 8.4 Extending ThreadPoolExecutor
      • Using hooks for extension
      • beforeExecute
      • afterExecute
      • terminate
      • Exceptions from ThreadPoolExecutor
    • 8.5 Parallelizing recursive algorithms
      • Converting sequential tasks to parallel
      • Using Fork/Join to execute tasks
  • 9 Fork/Join
    • Breaking up work into chunks
    • ForkJoinPool and ForkJoinTask
    • Work-stealing in ForkJoinPool
    • ForkJoinTask state machine
    • RecursiveTask vs RecursiveAction
    • Parallel Fibonacci Calculator
    • Fork/Join vs. Compute
    • Parallel merge sort
    • Canceling a task
    • Visibility guarantees with fork/join
    • Placing limits on forking
    • BigInteger.parallelMultiply()
    • Use cases of fork/join
  • 10 Avoiding Liveness Hazards
    • 10.1 Deadlock
      • The drinking philosophers
      • Causing a deadlock amongst philosophers
      • Resolving deadlocks
      • Discovering deadlocks
      • Lock-ordering deadlocks
      • Defining a global ordering
      • Dynamic lock order deadlocks
      • Defining order on dynamic locks
      • Checking whether locks are held
      • Imposing a natural order
      • Deadlock between cooperating objects
      • Open calls and alien methods
        • Example in Vector
      • Resource deadlocks
      • Thread-starvation deadlocks
    • 10.2 Avoiding and diagnosing deadlocks
      • Avoiding multiple locks
      • Using open calls
      • Unit testing for lock ordering deadlocks
      • Adding a sleep to cause deadlocks
      • Verifying thread deadlocks
      • Timed lock attempts
      • Deadlock analysis with thread dumps
      • Thread Dump Caveats
      • Deadlocks in Virtual Threads
    • 10.3 Livelock
  • 11 Testing Concurrent Programs
    • How to test concurrent code
    • BankAccount Example
    • Simple race condition
    • Types of concurrent tests
    • 11.1 Testing for correctness
      • Checking for data races
      • Automatic tooling
        • JChord
        • JavaRaceFinder
        • FindBugs
        • IntelliJ IDEA
        • False positives
      • Testing through bulk updates
      • Server HotSpot interference
      • Testing pitfalls
      • Controlling HotSpot and JIT
      • Turning off optimizations
      • Randomizing bulk operations
      • Testing field visibility
      • Single updates, with time delays
      • Pros and cons of various approaches
      • Examples of testing broken code
      • Testing for deadlocks
    • 11.2 Testing for performance
      • HotSpot tricks
        • Loop unrolling
        • Useless code elimination
        • Inlining of method calls
        • Lock eliding
        • Lock coarsening
        • Eliminating object creation
      • HotSpot interference in microbenchmarks
      • HotSpot method call threshold
      • HotSpot compile time
      • Getting the fastest most optimized code
      • MathRandomTest
  • 12 Performance and Scalability
    • 12.1 Thinking about performance
      • Effects of serial sections and locking
      • Performance vs scalability
      • How fast vs how much
      • Evaluating performance tradeoffs
    • 12.2 Amdahl's and Little's laws
      • Formula for Amdahl's Law
      • Utilization according to Amdahl
      • Maximum useful cores
      • Problems with Amdahl's law in practice
      • Formula for Little's Law
      • Applying Little's Law in practice
      • How threading relates to Little's Law
    • 12.3 Costs introduced by threads
      • Cache invalidation
      • Locking and unlocking
      • Memory barriers
    • 12.4 Reducing lock contention
      • Exclusive locks
      • Narrowing lock scope
      • Using ConcurrentHashMap
      • Performance comparisons
      • Reducing lock granularity
      • Lock splitting
        • in LinkedBlockingQueue
        • in ConcurrentLinkedQueue
      • Lock striping
        • in ConcurrentHashMap
        • in LongAdder
      • Alternatives to Exclusive Locks
        • ReadWriteLock
        • StampedLock
        • Concurrent collections
        • CopyOnWrite collections for mostly reads
        • Immutable objects
        • Atomic classes
        • LongAdder and LongAccumulator
      • How to monitor CPU utilization
      • Reasons why CPUs might not be loaded
      • How to find "hot locks"
      • Hotspot options for lock performance
  • 13 Explicit Locks
    • 13.1 Lock and ReentrantLock
      • ReentrantLock implementation
      • Using the owned lock
      • Using try-finally
      • tryLock and timed locks
      • Using try-lock to avoid deadlocks
      • Interruptible locking
    • 13.2 Performance considerations
      • Java 5 vs Java 6 performance
      • Throughput on contended locks
      • Uncontended performance
      • Heavily contended locks
    • 13.3 Fairness
      • Standard non-fair locking
      • Round-robin by OS
      • Barging
      • Fair Owned Locks in Java
      • Throughput of fair locks
    • 13.4 Synchronized vs ReentrantLock
      • Memory semantics
      • Ease of use
      • Prefer synchronized
      • Which to use with Virtual Threads
    • 13.5 Read-write locks
      • ReadWriteLock interface
      • Understanding system to avoid starvation
      • ReadWriteLock vs synchronzied/volatile
      • Up and downgrading locks
    • 13.6 StampedLock
      • What is StampedLock?
      • Pessimistic Exclusive Lock
      • Pessimistic Non-Exclusive Lock
      • Optimistic Non-Exclusive Read (no lock)
      • Writing with a StampedLock
      • Optimistic reading with a StampedLock
  • 14 Building Custom Synchronizers
    • 14.1 Managing state dependence
      • Single-threaded vs multi-threaded
      • Structure of blocking state-dependent actions
      • Example using bounded queues
      • Introducing condition queues
        • With monitor locks
    • 14.2 Using condition queues
      • Condition predicate
      • Lock
      • Condition queue
      • Waking up too soon
      • Missed signals
        • InterruptedException
      • notify() vs notifyAll()
      • Encapsulating condition queues
      • How wait() works for Virtual Threads
    • 14.3 Explicit condition objects
      • Condition interface
      • Benefits of explicit condition queues
      • Timed conditions
    • 14.4 AbstractQueuedSynchronizer (AQS)
      • Basis for other synchronizers
    • 14.4 ManagedBlocker
      • Desired parallelism in ForkJoinPool
      • ManagedUnboundedQueue
      • Where ManagedBlocker is used in JDK
  • 15 Atomic Variables and Nonblocking Synchronization
    • 15.1 Disadvantages of locking
      • Elimination of uncontended intrinsic locks
      • Volatile vs locking performance
      • Priority inversion
      • Volatile flushing
    • 15.2 Hardware support for concurrency
      • Optimistic locking
      • Compare-and-Swap (CAS)
      • Compare-and-Set
      • CAS support in the JVM
      • Managing conflicts with CAS
      • Using "Unsafe" to access memory directly
      • Java 9 VarHandles Instead of Unsafe
      • Shared cache lines
      • Performance advantage of padding
    • 15.3 Atomic variable classes
      • Optimistic locking classes
      • Very fast when not too much contention
      • Types of atomic classes
      • How do atomics work?
      • Atomic array classes
    • 15.4 Nonblocking algorithms
      • Definition of lock-free and wait-free
      • Nonblocking stack
      • Doing speculative work
      • accumulateAndGet()
      • Position with AtomicReference
      • Atomic field updaters
      • Updating with VarHandles
      • compareAndExchange
    • 15.5 Striped64
      • How it works
      • Memory Usage
      • LongAdder
        • Performance
      • LongAccumulator
      • BitUtils for Fast Positions
  • 16 Conclusion
    • Tips on where to learn more
    • Thank you!