Concurrency: Java Threads in C#

Overview

Both Java and C# provide advanced support for concurrency and parallelism, enabling developers to build efficient, high-performance applications. Despite their differences in syntax and ecosystem, the concurrency models in these two languages are surprisingly similar, making it easier for Java developers to transition to C#.

How concurrency and parallelism works in ...

... Java

Java provides multi-threading support through the Thread class and the Executor framework. The Thread class allows for direct thread management, while the Executor framework offers a higher-level abstraction for managing thread pools and tasks.

... C#

C# supports multi-threading through the Thread class and the Task Parallel Library (TPL). The Thread class allows for low-level control of thread creation and management, while the TPL provides a higher-level abstraction for managing concurrent tasks.

Threads

Let's start right off with an example of threads - you will notice how inherently similar they are implemented in Java and C#:

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running!");
    }
}

public class Program {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        
        thread.start();
    }
}

via GIPHY

CompletableFuture<T>

While threads are a low-level mechanism for concurrent programming, Java also provides a higher-level abstraction called CompletableFuture<T>. This class represents a future result of an asynchronous computation and allows developers to chain multiple asynchronous operations together. Luckily, C# has a similar feature called Task, which provides similar functionality together with async.

public class Program {
    public static void main(String[] args) {
        // Run the task asynchronously
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("Task is running asynchronously!");
        });

        // Wait for the task to complete
        future.join();
    }
}

ExecutorService => Task Parallel Library

Threads are well suited for low-level control and simple concurrent tasks. However, there are occasions where you need efficient management of multiple concurrent tasks, and ExecutorService provides better resource allocation and simplified thread management in such cases. Luckily, there is a very similar feature in C# called the Task Parallel Library (TPL), which provides a higher-level abstraction for managing concurrent tasks.

public class Program {
    public static void main(String[] args) {
        // Create a fixed thread pool with 5 threads
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        // Run 10 threads using the executor service
        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                System.out.println("Task is running in ExecutorService!");
            });
        }
    }
}

via GIPHY

Lastly: Semaphores

Semaphores are synchronization tools used to control access to a shared resource by multiple threads, limiting the number of concurrent accesses. We can use them to allow a fixed number of threads to proceed simultaneously. In the following code example, we use a semaphore to limit the number of concurrent threads to three. This ensures that no more than three threads can run simultaneously, managing access to a shared resource efficiently.

public class Program {
    private static final Semaphore semaphore = new Semaphore(3); // Allow up to 3 concurrent threads

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];

        for (int i = 0; i < 10; i++) {
            final int taskNum = i;
            threads[i] = new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("Task " + taskNum + " is running with Semaphore");
                    Thread.sleep(1000); // Simulate work
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    semaphore.release();
                }
            });
            threads[i].start();
        }

        // Wait for all threads to finish
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

Conclusion

Both Java and C# provide strong support for parallelism and concurrency with their respective tools and frameworks. In Java, you have options like Threads, ExecutorService, and CompletableFuture, while C# offers Threads, the Task Parallel Library (TPL), and async/await keywords. Although their syntax and implementation differ, the core principles are surprisingly similar. If you want to leverage your Java skills to learn C# or gain a deeper understanding of these powerful features, be sure to check out our Java to C# Transfer Course.

Happy coding!


Related glossary entry: Parallelism & Concurrency - Threads

Share: