Threads and Concurrency
Java provides Thread, Runnable, ExecutorService, and synchronized for safe concurrent programming; C uses POSIX pthreads manually.
Introduction
In this lesson, you'll learn about threads and concurrency in Java. Coming from C, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C, you're familiar with java provides thread, runnable, executorservice, and synchronized for safe concurrent programming; c uses posix pthreads manually..
Java has its own approach to java provides thread, runnable, executorservice, and synchronized for safe concurrent programming; c uses posix pthreads manually., which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Concurrency {
// AtomicInteger: lock-free counter
private static final AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException, ExecutionException {
// Modern: ExecutorService manages a thread pool
ExecutorService exec = Executors.newFixedThreadPool(2);
Callable<Void> task = () -> {
for (int i = 0; i < 100_000; i++) {
counter.incrementAndGet(); // atomic — no lock needed
}
return null;
};
Future<Void> f1 = exec.submit(task);
Future<Void> f2 = exec.submit(task);
f1.get(); // wait for completion
f2.get();
exec.shutdown();
System.out.println("counter=" + counter.get()); // 200000
// synchronized block (explicit lock)
int[] shared = {0};
Object lock = new Object();
synchronized (lock) {
shared[0]++;
}
}
}Comparing to C
Here's how you might have written similar code in C:
#include <pthread.h>
#include <stdio.h>
int counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
void *increment(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main(void) {
pthread_t t1, t2;
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&lock);
printf("counter=%d\n", counter); /* 200000 */
return 0;
}You may be used to different syntax or behavior.
Java threads are objects (Thread/Runnable); C pthreads use function pointers and pthread_t handles
You may be used to different syntax or behavior.
ExecutorService manages a thread pool — safer than creating threads manually (avoids thread-per-request overhead)
You may be used to different syntax or behavior.
AtomicInteger/Long/Reference replace mutex-protected counters — lock-free and faster
You may be used to different syntax or behavior.
synchronized(lock) { } replaces pthread_mutex_lock/unlock — automatically releases on exit, even on exceptions
You may be used to different syntax or behavior.
Future<T>.get() blocks until the task completes and returns the result or throws ExecutionException
Step-by-Step Breakdown
1. Thread vs Runnable
Prefer Runnable (task logic) over extending Thread. Pass it to new Thread() or an Executor. Thread.start() creates the OS thread; join() waits.
pthread_create(&t, NULL, fn, arg);
pthread_join(t, NULL);Runnable task = () -> System.out.println("hello from thread");
Thread t = new Thread(task);
t.start();
t.join(); // wait for completion2. ExecutorService
ExecutorService manages a pool of threads. submit() accepts Runnable or Callable and returns a Future. Call shutdown() when done.
pthread_create(&t1, NULL, work, NULL);
pthread_create(&t2, NULL, work, NULL);
// must manage threads manuallyvar exec = Executors.newFixedThreadPool(4);
Future<Integer> f = exec.submit(() -> compute());
int result = f.get(); // blocks until done
exec.shutdown();3. Atomic Variables
java.util.concurrent.atomic classes provide lock-free, thread-safe operations on individual values — faster than synchronized for counters.
pthread_mutex_lock(&lock); count++; pthread_mutex_unlock(&lock);AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // atomic ++
count.compareAndSet(5, 10); // CAS: if 5, set to 104. synchronized Blocks
synchronized holds a monitor lock for the duration of the block. It releases automatically on exit — even if an exception is thrown.
pthread_mutex_lock(&m);
// critical section
pthread_mutex_unlock(&m);private final Object lock = new Object();
private int counter;
void increment() {
synchronized (lock) {
counter++; // auto-released on exit
}
}Common Mistakes
When coming from C, developers often make these mistakes:
- Java threads are objects (Thread/Runnable); C pthreads use function pointers and pthread_t handles
- ExecutorService manages a thread pool — safer than creating threads manually (avoids thread-per-request overhead)
- AtomicInteger/Long/Reference replace mutex-protected counters — lock-free and faster
Key Takeaways
- Runnable/Thread for fire-and-forget; ExecutorService for managed thread pools with Future results
- AtomicInteger/Long provide lock-free thread-safe increments — use instead of synchronized for counters
- synchronized(lock) {} auto-releases on exception — safer than manual mutex lock/unlock
- Future<T>.get() blocks and returns the result; throws ExecutionException if the task threw