Master Java Concurrency & Redis: Thread Safety, Pools, and Data Consistency

This article explains Java thread‑safety concepts, how to use and configure ThreadPoolExecutor, Redis performance tricks, Zset internal structures, memory‑eviction policies, node‑failure handling, cache‑DB consistency patterns, and MySQL transaction ACID properties with MVCC differences, providing practical code examples throughout.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Master Java Concurrency & Redis: Thread Safety, Pools, and Data Consistency

1. What is thread safety and how to guarantee it?

Thread safety means that when multiple threads access the same code or data simultaneously, the program still behaves correctly and data remains consistent.

In simple terms, a class or method is thread‑safe if it produces correct logic and consistent data under concurrent calls.

Common thread‑safety problems fall into four categories:

Data race (e.g., count++) where the read‑modify‑write sequence can be interleaved, causing lost updates.

Read‑write inconsistency, where a thread reads partially updated data.

Visibility issues, where a thread’s changes remain in its CPU cache and other threads see stale values.

Instruction reordering, where the JVM or CPU changes execution order, leading to objects being published before fully initialized.

Typical solutions include using synchronized, explicit locks, atomic classes, and concurrent collections.

public synchronized void increment() {
    count++;
}

ReentrantLock provides explicit lock control, timeout, and fairness options.

Lock lock = new ReentrantLock();
try {
    lock.lock();
    count++;
} finally {
    lock.unlock();
}

Java’s java.util.concurrent.atomic package (e.g., AtomicInteger) uses CAS to guarantee atomicity.

Concurrent collections such as ConcurrentHashMap, CopyOnWriteArrayList, and BlockingQueue are internally synchronized or lock‑free.

The volatile keyword ensures immediate visibility of writes to other threads.

volatile boolean running = true;

2. How to use a thread pool and what parameters does it have?

The purpose of a thread pool is to reuse threads, reduce the overhead of creating and destroying them, and control concurrency.

In Java, a thread pool is typically created via ThreadPoolExecutor or the factory methods in Executors (e.g., newFixedThreadPool, newCachedThreadPool).

ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> {
    System.out.println(Thread.currentThread().getName() + " executing task");
});
pool.shutdown();

The core constructor of ThreadPoolExecutor is:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    corePoolSize,
    maximumPoolSize,
    keepAliveTime,
    TimeUnit.SECONDS,
    workQueue,
    threadFactory,
    handler);

Parameter explanations:

corePoolSize : the number of threads the pool tries to keep alive even when idle.

maximumPoolSize : the maximum number of threads the pool can create; excess tasks are queued or rejected.

keepAliveTime : how long idle threads above the core size are kept before being terminated.

TimeUnit : unit for keepAliveTime (seconds, milliseconds, etc.).

workQueue : the queue that holds waiting tasks (e.g., LinkedBlockingQueue, ArrayBlockingQueue).

threadFactory : customizes thread creation (naming, daemon status).

handler : rejection policy when the queue is full and the pool has reached its maximum size.

3. What are the thread‑pool rejection policies?

When the task queue is saturated, the pool applies one of four built‑in policies:

CallerRunsPolicy : the calling thread executes the rejected task.

AbortPolicy : the pool throws a RejectedExecutionException.

DiscardPolicy : the task is silently dropped.

DiscardOldestPolicy : the oldest queued task is discarded, then the new task is retried.

Custom policies can be created by implementing RejectedExecutionHandler.

4. Why is Redis so fast?

Official benchmarks show single‑threaded Redis can achieve around 100 000 operations per second.

Redis throughput benchmark
Redis throughput benchmark

Key reasons for the performance:

Most operations run entirely in memory using highly efficient data structures, making CPU not the bottleneck.

Single‑threaded execution eliminates lock contention and context‑switch overhead.

Redis uses I/O multiplexing (select/epoll) to handle many client sockets with one thread.

5. How is Zset implemented under the hood?

Zset uses either a compressed list (when the number of elements < 128 and each element < 64 bytes) or a skiplist for larger sets.

6. What is a skiplist and how does it work?

A skiplist is a multi‑level ordered linked list that provides O(log N) search by “skipping” over nodes.

Skiplist example
Skiplist example

Each level has forward pointers and a span value. Nodes also have a backward pointer for reverse traversal.

typedef struct zskiplistNode {
    sds ele;               // element value
    double score;          // weight
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

7. What happens when Redis runs out of memory?

Redis uses the maxmemory setting to limit usable RAM. If not set, Redis consumes all available memory until the OS kills the process.

When maxmemory is reached, Redis applies an eviction policy defined by maxmemory-policy:

Policy

Description

noeviction

Writes are rejected; reads still work (default).

allkeys-lru

Evicts the least‑recently‑used keys among all keys.

volatile-lru

Evicts LRU keys that have an expiration time.

allkeys-random

Randomly evicts any key.

volatile-random

Randomly evicts an expiring key.

volatile-ttl

Evicts the key with the shortest remaining TTL.

allkeys-lfu

Evicts the least‑frequently‑used keys.

volatile-lfu

Evicts the least‑frequently‑used expiring keys.

8. How to handle a failed Redis node?

In a single‑instance deployment, a node crash means total service loss. Recovery relies on manual or scripted restarts and, if persistence (RDB/AOF) is enabled, loading from the snapshot.

Production environments use replication or Sentinel:

Master‑Slave : promote a slave to master when the master fails.

Redis Sentinel : monitors nodes and performs automatic failover.

Redis Cluster : multiple masters with replicas; if a master fails, its replica is promoted automatically. If both master and replica fail, the slot enters a fail state requiring manual intervention.

9. How to keep MySQL and cache data consistent?

Typical pattern: write to the database first, then delete the cache (write‑through). Cache reads use a “cache‑aside” strategy: on miss, load from DB and populate the cache.

Because caching sacrifices strong consistency (CAP’s AP), eventual consistency is achieved through mechanisms such as:

Retrying cache‑deletion via a message queue.

Subscribing to MySQL binlog (e.g., using Canal) and deleting cache based on change events.

Cache invalidation flow
Cache invalidation flow

10. How to avoid cache‑thundering when deleting then rebuilding a key?

Two common solutions:

SingleFlight / distributed mutex : only one request fetches from DB and rebuilds the cache; others wait or receive a fallback.

String key = "user:123";
RLock lock = redisson.getLock("lock:" + key);
boolean ok = lock.tryLock(50, 500, TimeUnit.MILLISECONDS);
try {
    String v = redis.get(key);
    if (v != null) return v; // double‑check
    Data data = db.query(id);
    redis.setex(key, serialize(data), ttlWithJitter());
    return serialize(data);
} finally {
    if (ok) lock.unlock();
}

Logical expiration + background refresh : store data with a logical expiration timestamp. When expired, the first request triggers an async refresh while other requests continue serving stale data.

CacheVal cv = redis.get(key);
if (cv != null && now < cv.logicalExpireAt) return cv.data;
if (tryAcquireRefreshToken(key)) {
    async(() -> {
        Data fresh = db.queryById(123);
        redis.set(key, new CacheVal(fresh, now+softTtl), hardTtlWithJitter());
    });
}
return cv != null ? cv.data : fallback();

11. What are the ACID properties of a transaction and how does InnoDB guarantee them?

Atomicity : ensured by undo logs (rollback).

Consistency : derived from atomicity, isolation, and durability.

Isolation : provided by MVCC or locking mechanisms.

Durability : guaranteed by redo logs.

12. How does MVCC differ between READ COMMITTED and REPEATABLE READ?

READ COMMITTED : a new read view is created before each statement, so the same transaction may see different results on successive reads.

REPEATABLE READ : a single read view is generated at transaction start and used for the whole transaction, guaranteeing consistent reads.

13. Hand‑written longest common subsequence (LCS) example

The LCS problem finds the longest subsequence common to two strings.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavadatabaseredisThreadPool
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.