0% found this document useful (0 votes)
1 views40 pages

Optimizing Code in Java Chapter 3

The document covers multi-threading principles in Java, including sequential vs parallel execution, the use of threads, and parallel streams for optimized data processing. It also discusses advanced threading patterns such as thread pools and CompletableFutures, as well as caching strategies using Redis. Key concepts include lazy initialization and the singleton pattern to manage resource-intensive operations efficiently.

Uploaded by

ooafonrinwo
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views40 pages

Optimizing Code in Java Chapter 3

The document covers multi-threading principles in Java, including sequential vs parallel execution, the use of threads, and parallel streams for optimized data processing. It also discusses advanced threading patterns such as thread pools and CompletableFutures, as well as caching strategies using Redis. Key concepts include lazy initialization and the singleton pattern to manage resource-intensive operations efficiently.

Uploaded by

ooafonrinwo
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

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

You might also like