Concurrent Programming in java: Made Easy!

In Java, we can make our programs do more than one thing at a time. This is called concurrent programming in java or multitasking in programming. When we make our program handle many things at once, the concurrent programming in java can be faster and more responsive

Java has some simple tools to help with this, like threadsExecutorService, and Java concurrency tools. Let’s look at these with easy examples and short code snippets!

What is concurrent programming in java (multitasking in programming)?

Concurrent programming or multitasking in programming is when a program can do many tasks at the same time. This is helpful for apps that need to do multiple things at once. For example, in a video game, the game can keep playing while it loads new data in the background.

Java multithreading enables concurrent execution of tasks, improving application performance and responsiveness by leveraging multiple CPU cores

Java has built-in tools to help. We can use threads to run different tasks at the same time.

Using Threads in Java

thread is like a path for the code to run on. Here’s a simple example of how to make a thread in Java by extending the Thread class:

class MyThread extends Thread { 
  public void run() {
    System.out.println("Thread is running…"); 
  } 
} 
public class ThreadExample { 
  public static void main(String[] args) {
    new MyThread().start(); // Start the thread 
  } 
}

What’s Happening?

  • Thread: We made a thread by extending the Thread class.
  • run(): The code inside run() is what the thread will do.

Using the Runnable Interface

Another way to make a thread in Java is by using the Runnable interface. This way is very flexible.

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable thread is running...");
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        new Thread(new MyRunnable()).start(); // Start the thread
    }
}

What’s Happening?

  • Runnable: Here, we use Runnable to make the task that the thread will do.

Using ExecutorService to Manage Threads

The ExecutorService in Java makes it easier to manage threads. It lets us create a group of threads to handle many tasks at once.

import java.util.concurrent.*;

public class ExecutorServiceExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.submit(() -> System.out.println("Task by: " + Thread.currentThread().getName()));
        executor.shutdown();
    }
}

What’s Happening?

  • ExecutorService: We create a group of threads to run tasks at the same time.
  • shutdown(): This command stops the thread pool after all tasks are done.

Synchronizing Code in Java

When many threads share something, they need to take turns. Java uses synchronization to make this happen safely.

Here’s a simple example with a counter:

class Counter {
    private int count = 0;

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

    public int getCount() {
        return count;
    }
}

public class SyncExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(counter::increment);
        Thread t2 = new Thread(counter::increment);

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Final Count: " + counter.getCount());
    }
}

What’s Happening?

  • synchronized: This keyword makes sure only one thread can use the method at a time.

Using CountDownLatch in Java

Java has tools like CountDownLatch to help with tasks. CountDownLatch is part of the java.util.concurrent package.

CountDownLatch Example:

import java.util.concurrent.*;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);

        Runnable task = () -> {
            System.out.println("Task by: " + Thread.currentThread().getName());
            latch.countDown();
        };

        new Thread(task).start();
        new Thread(task).start();
        
        latch.await(); // Wait for tasks
        System.out.println("All tasks are done!");
    }
}

What’s Happening?

  • CountDownLatch: Every time a task is done, the latch counts down. await() waits until the count is zero.

Using Parallel Streams in Java 8

Java 8 has a new tool called parallel streams. It makes it easy to run tasks at the same time.

import java.util.Arrays;

public class ParallelStreamExample {
    public static void main(String[] args) {
        Arrays.asList(1, 2, 3).parallelStream()
              .forEach(num -> System.out.println("Processing: " + num));
    }
}

What’s Happening?

  • parallelStream(): This splits the tasks automatically to run at the same time.

Quick Tips for Concurrent Programming

  1. Use ExecutorService: It’s easier than managing threads on your own.
  2. Synchronize Shared Resources: Use synchronized when threads share things.
  3. Use Concurrency Utilities: Tools like CountDownLatch help manage tasks.
  4. Avoid Deadlocks: Make sure your code doesn’t block threads forever.

Project: Concurrent Banking System (concurrent programming in java)

Goal

Simulate a banking system where multiple threads represent transactions happening at the same time.

Steps

  1. Create a BankAccount class to represent an account with deposit and withdrawal methods.
  2. Use synchronized methods to ensure thread safety during transactions.
  3. Create a Transaction class that represents deposit and withdrawal actions.
  4. Run multiple transactions concurrently using Thread or ExecutorService.

Components of the Program:

  1. BankAccount Class:
    • Purpose: Manages a bank account’s balance.
    • Thread-Safety: The key challenge in concurrent programming is ensuring that multiple threads can access and modify shared resources (like a bank account’s balance) without causing data inconsistency. In this class, the deposit and withdraw methods are synchronized, meaning that only one thread can execute these methods at a time. This prevents multiple threads from modifying the balance simultaneously and causing errors.
    • Methods:
      • deposit(double amount): Adds money to the account balance.
      • withdraw(double amount): Deducts money from the account balance, as long as there’s enough money.
      • getBalance(): Returns the current balance of the account.
  2. Transaction Class:
    • Purpose: Represents a transaction (either a deposit or withdrawal) that will be run in a separate thread.
    • Runnable Interface: Implements Runnable so it can be executed by a thread. The run() method contains the logic to either deposit or withdraw money based on user input.
    • Attributes:
      • account: The BankAccount object where transactions are made.
      • isDeposit: A boolean indicating whether the transaction is a deposit (true) or withdrawal (false).
      • amount: The amount of money to deposit or withdraw.
  3. Main Class:
    • Purpose: The entry point of the program where the user can specify the initial balance and transactions.
    • Concurrency: Manages concurrent execution of multiple transactions using an ExecutorService. It creates a pool of threads to handle multiple transactions at once.
    • Transactions: For simplicity and testing, the transactions are hardcoded, but in a real scenario, they could be entered dynamically by the user. The transactions are submitted to the executor service, which runs them concurrently.
    • ExecutorService: A service to manage a pool of threads. In this case, a thread pool of 3 threads is created to execute multiple transactions concurrently.

Let’s see how it’s work! click here

Explanation of the code!

Components of the Program:

  1. BankAccount Class:
    • Purpose: Manages a bank account’s balance.
    • Thread-Safety: The key challenge in concurrent programming is ensuring that multiple threads can access and modify shared resources (like a bank account’s balance) without causing data inconsistency. In this class, the deposit and withdraw methods are synchronized, meaning that only one thread can execute these methods at a time. This prevents multiple threads from modifying the balance simultaneously and causing errors.
    • Methods:
      • deposit(double amount): Adds money to the account balance.
      • withdraw(double amount): Deducts money from the account balance, as long as there’s enough money.
      • getBalance(): Returns the current balance of the account.
  2. Transaction Class:
    • Purpose: Represents a transaction (either a deposit or withdrawal) that will be run in a separate thread.
    • Runnable Interface: Implements Runnable so it can be executed by a thread. The run() method contains the logic to either deposit or withdraw money based on user input.
    • Attributes:
      • account: The BankAccount object where transactions are made.
      • isDeposit: A boolean indicating whether the transaction is a deposit (true) or withdrawal (false).
      • amount: The amount of money to deposit or withdraw.
  3. Main Class:
    • Purpose: The entry point of the program where the user can specify the initial balance and transactions.
    • Concurrency: Manages concurrent execution of multiple transactions using an ExecutorService. It creates a pool of threads to handle multiple transactions at once.
    • Transactions: For simplicity and testing, the transactions are hardcoded, but in a real scenario, they could be entered dynamically by the user. The transactions are submitted to the executor service, which runs them concurrently.
    • ExecutorService: A service to manage a pool of threads. In this case, a thread pool of 3 threads is created to execute multiple transactions concurrently.

How the Program Works in concurrent programming:

  1. Initialize Bank Account:
    • When the program starts, a BankAccount object is created with an initial balance (hardcoded for testing purposes).
  2. Creating Transactions:
    • Multiple Transaction objects are created. Each Transaction object either deposits or withdraws a specific amount of money from the BankAccount. These transactions are executed in separate threads to simulate real-time concurrent operations.
  3. Executor Service:
    • The ExecutorService is used to manage threads. Instead of creating and managing each thread manually, we use a thread pool that can execute multiple tasks concurrently. Each transaction (deposit or withdrawal) is submitted to the executor for execution.
    • The program uses submit() to add tasks to the executor pool. This allows the transactions to be processed concurrently, meaning multiple transactions can occur at the same time.
  4. Synchronized Methods:
    • The deposit and withdraw methods in the BankAccount class are synchronized. This means that only one thread can execute these methods at a time, preventing concurrent modifications to the account balance. For example, if two threads attempt to withdraw money from the same account at the same time, synchronization ensures that one thread finishes its operation before the other begins.
  5. Concurrency in Action:
    • As each Transaction is executed in a different thread, the program demonstrates how concurrent programming can handle multiple transactions (deposits and withdrawals) at once while ensuring that the account balance is updated correctly.
    • The final output shows the sequence of transactions and how the balance changes after each operation.

Interview Questions:


1. Company: TCS


Question: How do you mitigate the performance impact when using reflection in large-scale applications at TCS?


Answer: To mitigate performance impact, reflection should be used sparingly and results cached for repeated access. Limiting its use in performance-critical code helps maintain efficiency in large-scale TCS applications.


2. Company: Infosys


Question: What is the difference between Class.forName(), getClass(), and getDeclaredMethods() in Java Reflection?


Answer: Class.forName() dynamically loads a class at runtime, while getClass() retrieves the runtime class of an object. getDeclaredMethods() returns all methods in a class, including private and protected ones.


3. Company: Zoho


Question: How would you use Java Reflection to dynamically call a method on an object without knowing the method name at compile time?


Answer: Use getDeclaredMethod() to get a method by name and parameter types, then invoke it using method.invoke(). This allows calling methods dynamically at runtime without compile-time knowledge.


4. Company: Zoho


Question: What is Java Reflection, and how does it work?


Answer: Java Reflection is an API that allows inspecting and modifying classes, methods, and fields during runtime. It uses the Class object to interact with class metadata and dynamically change behaviors.


5. Company: TCS


Question: How does Java Reflection interact with Java access control mechanisms like private, protected, and public members?


Answer: Reflection bypasses access control by using setAccessible(true) to access private and protected members. However, this should be done carefully to maintain security and encapsulation in TCS applications.


Conclusion

In concurrent programming in java.With threadsRunnableExecutorService, and Java concurrency tools, you can make your programs faster and more responsive. This guide gives you a simple start with examples to try out. Happy coding!