Thread Safety vs Concurrency in Java: Know the Difference and Write Better Multithreaded Code

Illustration for Thread Safety vs Concurrency in Java: Know the Difference and Write Better Multithreaded Code
By Last updated:

"Thread safety" and "concurrency" are often used interchangeably, but they are not the same. In Java multithreading, understanding the difference between them is critical to writing robust, efficient, and correct applications.

This guide dives deep into what thread safety and concurrency really mean, how they differ, when they overlap, and how to master both in practice.


🧠 Definitions: What They Actually Mean

🔒 Thread Safety

A thread-safe program behaves correctly when accessed by multiple threads, regardless of the timing or interleaving of those threads. It avoids issues like:

  • Race conditions
  • Visibility problems
  • Inconsistent state

Thread safety focuses on correctness under concurrency.

🧵 Concurrency

Concurrency is about doing multiple tasks at once. It enables performance improvements by overlapping operations (CPU, I/O, etc.), and is achieved using:

  • Multiple threads
  • Asynchronous processing
  • Parallelism (when combined with multiple cores)

Concurrency focuses on performance and responsiveness.


🔍 Key Differences

Feature Thread Safety Concurrency
Primary Goal Correctness Performance
Required For Concurrency No Yes
Requires Synchronization Usually Not necessarily
Example Concern Preventing race conditions Processing tasks in parallel
Analogy Secure bank vault access Multiple bank tellers

⚙️ Thread Lifecycle Context

State Thread-Safe Impact Concurrent Usage
NEW Not yet affecting shared state Preparing tasks
RUNNABLE May read/write shared data Doing parallel work
BLOCKED Waiting on locks Can reduce performance
WAITING Waiting for coordination Common in producer-consumer
TERMINATED Thread complete Completed task

🔐 Ensuring Thread Safety

Option 1: synchronized

public synchronized void add(int value) {
    this.total += value;
}

Option 2: ReentrantLock

lock.lock();
try {
    this.total += value;
} finally {
    lock.unlock();
}

Option 3: Atomic Variables

AtomicInteger count = new AtomicInteger();
count.incrementAndGet();

Option 4: Concurrent Collections

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

🧰 Leveraging Concurrency

Thread + Runnable

new Thread(() -> doWork()).start();

ExecutorService

ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> doWork());

Fork/Join Framework

ForkJoinPool pool = ForkJoinPool.commonPool();
pool.submit(new RecursiveTask<>());

Virtual Threads (Java 21)

Thread.startVirtualThread(() -> fetchData());

⚠️ Common Pitfalls

  • Thread-safe ≠ concurrent: A synchronized block may be thread-safe but serializes access (no concurrency).
  • Concurrent ≠ thread-safe: Running tasks in parallel on shared state can corrupt data if not managed correctly.
  • Over-synchronization: Reduces concurrency and causes contention.
  • Under-synchronization: Leads to data races and undefined behavior.

📦 Real-World Scenarios

✅ Thread-Safe But Not Concurrent

public synchronized void writeLog(String message) {
    writer.write(message);
}

Only one thread can log at a time—safe but not concurrent.


✅ Concurrent But Not Thread-Safe

List<String> list = new ArrayList<>();

Runnable task = () -> list.add("value"); // Race condition!

Multiple threads run in parallel but corrupt shared list.


✅ Both Thread-Safe and Concurrent

ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();

Allows safe parallel access.


📌 What's New in Java?

Java 8

  • Lambdas for Runnable, Callable
  • ConcurrentHashMap.computeIfAbsent()
  • CompletableFuture

Java 9

  • Flow API (Reactive Streams)

Java 11

  • Minor improvements to CompletableFuture and HTTP client

Java 17

  • Pattern matching and sealed classes for thread-safe APIs

Java 21

  • ✅ Virtual Threads
  • ✅ Structured Concurrency
  • ✅ Scoped Values for better context isolation

✅ Best Practices

  • Identify shared mutable state and guard it
  • Use immutable objects whenever possible
  • Favor high-level concurrency utilities (ExecutorService, ConcurrentHashMap)
  • Apply synchronization only when necessary
  • Validate performance impact of locks
  • Monitor thread contention and CPU usage with profiling tools

🚫 Anti-Patterns

  • Using ArrayList or HashMap with multiple threads
  • Overusing synchronized on public methods
  • Ignoring volatile or final for visibility
  • Blindly using thread pools without sizing
  • Assuming single-threaded code is safe when ported to parallel model

🧰 Design Patterns That Help

  • Worker Thread: Submit work to a pool
  • Immutable Object: Eliminate need for synchronization
  • Guarded Suspension: Wait until condition holds
  • Monitor Object: Encapsulate synchronized access
  • Thread-Local Storage: Store per-thread variables

📘 Conclusion and Key Takeaways

  • Thread safety ensures correctness under concurrency
  • Concurrency enables performance, not correctness
  • A system can be one, both, or neither
  • Use the right tool for the job—choose between locking, atomicity, or high-level constructs
  • With Java 21, thread safety and concurrency are easier to manage using virtual threads and structured concurrency

❓ FAQ

1. Can I write concurrent code without threads?

Yes, via async programming, reactive streams, or coroutines.

2. Is synchronized always thread-safe?

Not necessarily—depends on logic inside and how shared state is used.

3. What if I use volatile only?

It ensures visibility, not atomicity.

4. Is ConcurrentHashMap always safe?

Yes, for individual operations. Compound actions may still need synchronization.

5. Are virtual threads thread-safe?

Virtual threads are lightweight, but thread safety rules still apply.

6. Can I mix concurrent and non-concurrent code?

Yes, but shared state access must be guarded appropriately.

7. Why does thread safety reduce performance?

Because it limits concurrency via synchronization or blocking.

8. How to know if code is thread-safe?

Test with race condition tools, code audits, and use safe data structures.

9. Can immutable objects be shared freely?

Yes. They are inherently thread-safe.

10. Is JavaScript concurrent?

JavaScript is single-threaded but asynchronous. Not concurrent like Java threads.