Multithreading
Multitasking means ability of the computer to perform multiple tasks simultaneously. For instance, while listening to music, you can simultaneously use a paint application to create art. Multitasking can process-based or thread-based.
Process-based multitasking involves running multiple process independently. For example, running different applications like a web browser, word processor, and media player simultaneously on a computer.
Thread- based multitasking involves running multiple threads within the same process. ie multiple path of execution within the same process. Threads share the same memory space and resources. For instance, a word processor application might use multiple threads to handle tasks such as spell checking, formatting, and printing simultaneously.
Threads share same address space. Hence context switching between threads is less expensive than the processes and the cost of communication between the threads is relatively low.
A thread is an independent sequential path of execution in a process.
When an application runs, a main thread is automatically created and if no child threads are spawned, the program terminates once main thread completes its execution. If the child threads or user threads are spawned, the main method can finish but program will still run until child threads are completed.
Threads can be user thread or daemon thread. The thread class marks the status of thread as daemon thread or user thread. A program will run if there are user thread is not completed but it will terminate when all the user threads and main thread are completed and daemon thread is not completed.
There are two ways to create a thread.
- Extending the Thread class
- Implementing Runnable interface
Main.java
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
MultiThreading1 multiThread = new MultiThreading1();
Thread thread = new Thread(multiThread);
thread.start();
}
}
}
MultiThreading1.java
public class MultiThreading1 implements Runnable {
private int threadNumber;
public MultiThreading1(int threadNumber) {
this.threadNumber = threadNumber;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Thread Number"+ threadNumber +": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
Main.java
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
MultiThreading1 thread = new MultiThreading1(i);
thread.start();
}
}
}
MultiThreading2.java
public class MultiThreading2 extends Thread{
private int threadNumber;
public MultiThreading2(int threadNumber) {
this.threadNumber = threadNumber;
}
@Override public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("Thread Number"+ threadNumber +": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
Synchronization
Race Condition
They are particularly common in concurrent programming, where multiple threads or processes are executing simultaneously and accessing shared resources, such as variables or files, leave the value in undefined or inconsistent state. The race condition can be prevented
- Synchronized Methods: Use the synchronized keyword to make methods thread-safe. When a thread enters a synchronized method, it acquires the intrinsic lock associated with the object and releases it when the method exits.
- Synchronized Blocks: Use synchronized blocks to protect critical sections of code. This allows for finer-grained control over synchronization compared to synchronizing entire methods.
- Atomic Variables: Use atomic classes from the
java.util.concurrent.atomicpackage, such asAtomicInteger, to perform atomic operations without explicit locking. - Lock Objects: Use explicit lock objects from the
java.util.concurrent.lockspackage. This provides more flexibility and control over locking compared to intrinsic locks.
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.atomic.AtomicInteger;
/**
* In this example, we have a Counter class with a count variable that represents
* a simple counter.
* We then create two threads, thread1 and thread2, that increment the
* counter concurrently by calling the increment() method.
* However, since both threads are accessing and modifying the count variable
* without proper synchronization, a race condition occurs.
* As a result, the final count printed may not always be 2000, as expected.
* Instead, it may vary due to the interleaved execution of the threads and the* unpredictable ordering of their operations.
*/
class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
/**
* Solution 1 for race condition
*
* However, since both threads are accessing and modifying the count variable with
* proper synchronization method, the final count printed will always be 2000,
* as expected.
*
*/
class CounterSynchronized {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
/**
* Solution 2 for race condition
*
* However, since both threads are accessing and modifying the count variable
* with proper synchronization block, the final count printed will always be 2000,
* as expected.
*
*/
class CounterSynchronizedBlock {
private int count = 0;
public void increment() {
synchronized(this){
count++;
}
}
public int getCount() {
return count;
}
}
/**
* Solution 3 for race condition
*
* However, since both threads are accessing and modifying the count variable which
* is Atomic variable, to perform atomic operations without explicit locking.* The final count printed will always be 2000, as expected.
*
*/
class CounterAtomic {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
public class RaceCondition {
public static void main(String[] args) {
// Counter counter = new Counter();
// CounterSynchronizedBlock counter = new CounterSynchronizedBlock();
// CounterSynchronized counter = new CounterSynchronized();
// CounterAtomic counter = new CounterAtomic();
CounterLock counter = new CounterLock();
// Creating two threads to increment the counter concurrently
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
// Starting both threads
thread1.start();
thread2.start();
// Waiting for both threads to complete
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Printing the final count
System.out.println("Final count: " + counter.getCount());
}
}
Deadlock
Livelock
Comments
Post a Comment