Basic multi-
threading principles
O P T I M I Z I N G C O D E I N J AVA
Pavlos Kosmetatos
Lead Engineer @Wealthyhood
Sequential vs parallel execution
Sequential processing -> operations happen one after another
Parallel processing -> multiple operations happen simultaneously
How?
Modern CPUs have multiple cores
Thread: the smallest unit of program execution
Multi-threading distributes work across these cores
Single-threading is like having a single-lane road where cars must follow one another. Multi-
threading is like having multiple lanes where cars can travel simultaneously!
OPTIMIZING CODE IN JAVA
Using threads
Thread class allows creating new execution paths
Each Thread can execute independently
Runnable task = () -> {
[Link]("Processing on thread: " +
[Link]().getName());
};
Thread thread = new Thread(task);
[Link]();
Processing on thread: Thread-0
OPTIMIZING CODE IN JAVA
Working with multiple threads
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> [Link]("Processing data on Thread-" + i));
[Link](thread);
[Link]();
}
for (Thread t : threads) {
[Link](); // Waits for all threads to complete
}
// Processing data on Thread-0
// Processing data on Thread-2
// Processing data on Thread-1
// Processing data on Thread-3
OPTIMIZING CODE IN JAVA
Parallel streams
Streams - Java 8+ feature for simplified parallelism
Automatically handles thread creation and management
Two ways to create:
[Link]()
[Link](...).parallel()
OPTIMIZING CODE IN JAVA
Parallel streams example
// Sequential processing
List<Integer> result1 = new ArrayList<>();
for (int i = 0; i < [Link](); i++) {
[Link]([Link](i) * 2);
}
// Sequential processing with stream
List<Integer> result2 = [Link]()
.map(n -> n * 2)
.collect([Link]());
// Parallel processing with parallel stream
List<Integer> result3 = [Link]()
.map(n -> n * 2)
.collect([Link]());
OPTIMIZING CODE IN JAVA
When to use parallel processing
CPU-intensive operations
Independent data processing
Large data collections
Available CPU cores > 1
Parallelization overhead may not be worth it for:
Small data sets
Simple operations
OPTIMIZING CODE IN JAVA
Summary
Thread class for creating parallel execution paths
Parallel streams for simplified collection processing
Benefits depend on:
Workload type
Data size
Available cores
OPTIMIZING CODE IN JAVA
Let's practice!
O P T I M I Z I N G C O D E I N J AVA
Advanced threading
patterns
O P T I M I Z I N G C O D E I N J AVA
Pavlos Kosmetatos
Lead Engineer @Wealthyhood
Thread pool fundamentals
Thread creation is expensive!
Thread pools:
Reuse existing threads
Control number of concurrent threads
Are managed through ExecutorService interface
OPTIMIZING CODE IN JAVA
Creating thread pools
// Fixed thread pool with 4 threads
ExecutorService fixedPool = [Link](4);
// Cached thread pool that grows as needed
ExecutorService cachedPool = [Link]();
// Single-threaded executor
ExecutorService singleExecutor = [Link]();
OPTIMIZING CODE IN JAVA
Submitting tasks
ExecutorService executor = [Link](4);
// Submit a task with no return value
[Link](() -> [Link]("Simple task"));
// Submit a task with a return value (Callable)
Future<Integer> future = [Link](() -> {
[Link](1000);
return 42;
});
// Get result from Future (blocks until complete)
int result = [Link]();
OPTIMIZING CODE IN JAVA
Shutting down executors
// Signal shutdown, but continue running existing tasks
[Link]();
// Wait for termination (with timeout)
boolean terminated = [Link](5, [Link]);
// Force immediate shutdown, canceling running tasks
[Link]();
OPTIMIZING CODE IN JAVA
The completable future
CompletableFuture
Part of Java's concurrency API since Java 8
Modern approach to asynchronous programming
Can be completed manually or via a Function
Allows chaining operations with callbacks
Works with or without explicit thread pools
OPTIMIZING CODE IN JAVA
Creating completable futures
// Run async with default executor
CompletableFuture<Void> runAsync =
[Link](() -> performTask());
// Supply async with custom executor
ExecutorService executor = [Link]();
CompletableFuture<String> supplyAsync =
[Link](() -> fetchData(), executor);
OPTIMIZING CODE IN JAVA
Chaining operations
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> fetchUserData(userId))
.thenApply(data -> extractUsername(data))
.exceptionally(ex -> "Unknown user");
// Access to result when ready
[Link](result -> [Link](result));
OPTIMIZING CODE IN JAVA
Let's practice!
O P T I M I Z I N G C O D E I N J AVA
Caching strategies
O P T I M I Z I N G C O D E I N J AVA
Pavlos Kosmetatos
Lead Engineer @Wealthyhood
What is caching?
Caching is like keeping our frequently used cooking ingredients on the kitchen counter instead
of in the cupboard - it's faster to access, but we have limited counter space
We commonly cache:
Database query results
API responses
Expensive calculations
Other resource-intensive operations
OPTIMIZING CODE IN JAVA
In-memory caching in Java
// In-memory cache using HashMap
public class SimpleCache<K, V> {
private final Map<K, V> cache = new HashMap<>();
public V get(K key) {
return [Link](key);
}
public void put(K key, V value) {
[Link](key, value);
}
}
OPTIMIZING CODE IN JAVA
Cache eviction policies
LRU (Least Recently Used)
LFU (Least Frequently Used)
FIFO (First In, First Out)
Time-based expiration
And more!
OPTIMIZING CODE IN JAVA
Redis for distributed caching
What is Redis:
In-memory data store/cache
Excels as a distributed cache
Supports many data structures
Many Java client libraries - one example is Jedis
OPTIMIZING CODE IN JAVA
Using Redis with Jedis
import [Link];
// Using Jedis client
Jedis jedis = new Jedis("localhost");
[Link]("key", "value");
String value = [Link]("key");
Additional features:
Automatic expiration of cached items
Cluster support
OPTIMIZING CODE IN JAVA
Implementing a time-based cache with Redis
public class RedisTimedCache {
private final Jedis jedis;
public RedisTimedCache(String host, int port) {
[Link] = new Jedis(host, port);
}
public String get(String key) {
return [Link](key);
}
public void put(String key, String value, int timeToLiveSeconds) {
// Sets both the value and expiration time in seconds
[Link](key, timeToLiveSeconds, value);
}
}
OPTIMIZING CODE IN JAVA
Summary
Caching stores computed results to avoid recalculation
Effective for expensive operations and frequently accessed data
Implement proper eviction policies to manage memory
Consider distributed caching for multi-server applications (e.g. Redis with Jedis)
OPTIMIZING CODE IN JAVA
Let's practice!
O P T I M I Z I N G C O D E I N J AVA
Lazy initialization
and singleton
patterns
O P T I M I Z I N G C O D E I N J AVA
Pavlos Kosmetatos
Lead Engineer @Wealthyhood
Building a Redis cache client
Imagine we are building a cache client that hits Redis
Our Redis client is hosted at a third-party provider
Setting up the connection to our client involves a network call - imagine this takes 500ms
OPTIMIZING CODE IN JAVA
A simple (eager) implementation
public class RedisCache {
// The client library we use to connect to Redis
private final RedisClient client;
public RedisCache() {
// The connection is setup inside the constructor
connection = new RedisClient("[Link]");
}
}
Eager initialization of a Redis connection
OPTIMIZING CODE IN JAVA
A potential issue with this approach
This seems simple - and would work - but it has a potential issue
What if we don't need the Redis client at all?
We wasted precious time at startup
We established unnecessary connections
OPTIMIZING CODE IN JAVA
Lazy initialization
public class RedisCache {
private RedisClient client;
// Instead of setting up the connection in the constructor,
// we only set it up when someone needs to get the client.
public RedisClient getClient() {
if (connection == null) {
connection = new RedisClient("[Link]");
}
return connection;
}
}
OPTIMIZING CODE IN JAVA
Another issue with our approach
// UserService needs cache access
public class UserService {
private RedisCache userCache = new RedisCache(); // First connection
}
// PaymentService also needs cache
public class PaymentService {
private RedisCache paymentCache = new RedisCache(); // Second connection
}
// ...same for OrderService ...
OPTIMIZING CODE IN JAVA
The singleton pattern
public class RedisCache {
private static RedisCache instance;
private RedisClient client;
// The constructor is private so that we ensure we only
// create RedisCache inside this class
private RedisCache() {}
public static RedisCache getInstance()
// We only create a RedisCache if one does not already exist
if (instance == null) { instance = new RedisCache(); }
return instance;
}
// ... The rest is the same as before ...
}
OPTIMIZING CODE IN JAVA
Let's practice!
O P T I M I Z I N G C O D E I N J AVA
Wrap-up
O P T I M I Z I N G C O D E I N J AVA
Pavlos Kosmetatatos
Lead Engineer @Wealthyhood
Chapter 1: Fundamentals of code performance
Big-O Notation
Time Complexity
Space Complexity
Data Structure Selection
HashSet
HashMap
OPTIMIZING CODE IN JAVA
Chapter 2: Measuring code performance
[Link]() for precise timing
JVM Architecture
Performance Metrics
[Link]() for memory tracking
ThreadMXBean for CPU analysis
OPTIMIZING CODE IN JAVA
Chapter 3: Improving code performance
Multi-threading Fundamentals
Parallel streams
ExecutorService
Caching Strategies
Jedis (Redis)
Optimization Patterns
Singleton pattern implementation
Lazy vs eager initialization
OPTIMIZING CODE IN JAVA
Thank you!
O P T I M I Z I N G C O D E I N J AVA