Thread + Concurrency
Thread + Concurrency
**Concurrency** refers to the ability to run multiple threads simultaneously, which can be
particularly useful for tasks that can be parallelized or that involve waiting (like I/O
operations). Java provides built-in support for concurrency using the **Thread** class,
**Runnable interface**, and higher-level utilities like **ExecutorService**.
Below is an example of how to create and run concurrent tasks in Java using different methods.
In this example, we extend the `Thread` class and override the `run()` method to define the
task that will be executed concurrently.
```
class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getId() + " Value: " + i);
Thread.sleep(1000); // Simulate a task that takes 1 second
}
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
**Explanation:**
- We create a `MyThread` class that extends `Thread` and overrides the `run()` method.
- Inside the `run()` method, we simulate a task by printing numbers with a 1-second delay using
`Thread.sleep(1000)`.
- Two instances of `MyThread` are created and started using the `start()` method, which will run
the threads concurrently.
```
class MyRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getId() + " Value: " + i);
Thread.sleep(1000); // Simulate a task that takes 1 second
}
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
**Explanation:**
- `MyRunnable` implements the `Runnable` interface and overrides the `run()` method.
- We create two threads `t1` and `t2` using the `Runnable` object (`task`).
- The threads run concurrently and execute the task defined in `run()`.
```
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
executorService.shutdown(); // Shut down the executor service once tasks are done
}
}
```
**Explanation:**
- An `ExecutorService` is created using the `Executors.newFixedThreadPool(2)` method,
which creates a pool of 2 threads.
- We submit two tasks to the executor. These tasks will be run by the threads in the pool.
- After submitting the tasks, we call `shutdown()` to indicate that no more tasks will be
submitted.
### Example 4: Using `Callable` and `Future` for Return Values
If your concurrent tasks need to return a result or throw an exception, you can use the
`Callable` interface instead of `Runnable`. `Callable` allows tasks to return a result and is often
used with `Future` to get the result of the task.
```
import java.util.concurrent.*;
**Explanation:**
- We create a `MyCallable` class that implements the `Callable` interface. This interface allows
us to return a result (in this case, an integer sum).
- We submit the `MyCallable` tasks to an `ExecutorService`.
- We use the `get()` method of the `Future` object to retrieve the result of each task. This blocks
the main thread until the task completes.
When multiple threads access shared resources, you need to ensure thread safety. You can use
`synchronized` blocks or methods to prevent race conditions.
```
class Counter {
private int count = 0;
// Synchronized method ensures that only one thread can access it at a time
public synchronized void increment() {
count++;
}
t1.start();
t2.start();
t1.join();
t2.join();
**Explanation:**
- The `increment` method is marked as `synchronized`, ensuring that only one thread can
increment the `count` at a time.
- We create two threads that each increment the counter 1000 times.
- The `join()` method ensures that the main thread waits for both threads to finish before printing
the final count.
- **Thread** and **Runnable** are the basic building blocks for concurrency in Java.
- Use an **ExecutorService** for managing a pool of threads and tasks, which simplifies
thread management.
- **Callable** is useful when tasks need to return a result.
- **Synchronization** is necessary to ensure thread safety when accessing shared
resources.
Java Concurrency Interview Questions
Java concurrency assures us to do some tasks even faster by dividing these tasks
into subtasks. After that, these subtasks are executed synchronously.
These are some of the important Java concurrency questions which help
candidates to crack Java interviews.
If one or more events are used in other threads, and we want to use those events
in the currently executing thread, we can push that thread in the waiting state by
using Countdownlatch until the threads that are using those events are not
complete.
The thread uses acquire() method for acquiring permits to get access to the
shared resource. At the same time, if the value of the Semaphore's count is not
equal to 0, the count value of the Semaphore will be decremented by one and
acquire a permit. Else the thread will be blocked until a permit is available. The
thread uses the release() method to release the shared resources when it is done
with it.
It provides the following two methods for executing the Callable and Runnable
task after a specified time period:
This method is mainly used for creating thread pools that use previously created
threads or create new ones as needed.
This method is also used for creating thread pools that use the fixed size of
previously created threads.
This method is also used to create a thread pool for scheduling commands to
execute periodically or to run after a specified time.
4. The newSingleThreadExecutor()
This method is mainly used for creating an Executor. The created executor uses a
single worker thread operating off an unbounded queue.
BlockingQueue has the following two methods for blocking the operations.
1. The put(element) is used to insert the specified element into the queue.
2. The take() method is used to retrieve and remove the head from the
queue.