C
JV

C to Java

10 lessons

Progress0%
1Introduction to Java2Data Types3Strings4Arrays and Collections5Object-Oriented Programming6Exception Handling7Collections and Generics8Modern Java Features9Interfaces and Polymorphism10Threads and Concurrency
All Mirror Courses
C
JV
Threads and Concurrency
MirrorLesson 10 of 10
Lesson 10

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.

Mirror Card
C
From C:

In C, you're familiar with java provides thread, runnable, executorservice, and synchronized for safe concurrent programming; c uses posix pthreads manually..

JV
In Java:

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:

JV
Java 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:

C
C (What you know)
#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;
}
Mirror Card
C
From C:

You may be used to different syntax or behavior.

JV
In Java:

Java threads are objects (Thread/Runnable); C pthreads use function pointers and pthread_t handles

Mirror Card
C
From C:

You may be used to different syntax or behavior.

JV
In Java:

ExecutorService manages a thread pool — safer than creating threads manually (avoids thread-per-request overhead)

Mirror Card
C
From C:

You may be used to different syntax or behavior.

JV
In Java:

AtomicInteger/Long/Reference replace mutex-protected counters — lock-free and faster

Mirror Card
C
From C:

You may be used to different syntax or behavior.

JV
In Java:

synchronized(lock) { } replaces pthread_mutex_lock/unlock — automatically releases on exit, even on exceptions

Mirror Card
C
From C:

You may be used to different syntax or behavior.

JV
In Java:

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.

C
C
pthread_create(&t, NULL, fn, arg);
pthread_join(t, NULL);
JV
Java
Runnable task = () -> System.out.println("hello from thread");
Thread t = new Thread(task);
t.start();
t.join();  // wait for completion

2. ExecutorService

ExecutorService manages a pool of threads. submit() accepts Runnable or Callable and returns a Future. Call shutdown() when done.

C
C
pthread_create(&t1, NULL, work, NULL);
pthread_create(&t2, NULL, work, NULL);
// must manage threads manually
JV
Java
var 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.

C
C
pthread_mutex_lock(&lock); count++; pthread_mutex_unlock(&lock);
JV
Java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();         // atomic ++
count.compareAndSet(5, 10);      // CAS: if 5, set to 10

4. synchronized Blocks

synchronized holds a monitor lock for the duration of the block. It releases automatically on exit — even if an exception is thrown.

C
C
pthread_mutex_lock(&m);
// critical section
pthread_mutex_unlock(&m);
JV
Java
private final Object lock = new Object();
private int counter;

void increment() {
    synchronized (lock) {
        counter++;  // auto-released on exit
    }
}
Rule of Thumb
Prefer AtomicInteger for counters, ConcurrentHashMap for maps, and BlockingQueue for producer-consumer — they outperform manual synchronization.

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
Common Pitfall
Don't assume Java works exactly like C. While the concepts may be similar, the syntax and behavior can differ significantly.

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
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your C code in Java to practice these concepts.
PreviousFinish