Java MultiThreading Example Code
Java MultiThreading Example Code
The wait() method, when called within a synchronized block, causes the current thread to release its lock on the object and enter a waiting state until another thread issues a notify() on the same object. This mechanism prevents the current thread from proceeding, effectively blocking its execution. This allows another thread to acquire the lock and proceed with execution, ensuring that shared resources are used efficiently. In the given example, if thread 0 attempts to execute with n = 2, it will call wait(), subsequently blocking its flow until thread 1 completes its execution and calls notify().
The run() method in the thread class is an implementation that fulfills the contract defined by the Runnable interface. The Runnable interface requires implementing classes to provide a concrete definition of the run() method, which contains the code to be executed by the thread. In the MyThread class, which extends the Thread class, the run() method is overridden to invoke the print() method of the PrintTable instance, ensuring that the intended sequence of operations is executed by the thread .
If the call to wait() is omitted in the synchronized block of the PrintTable class, the initial control flow and intended resource access coordination will change. Both threads would attempt to proceed with printing immediately upon obtaining the lock, which might result in overlapping execution and disrupted intended sequence operations. The intended synchronization, where thread 1 completes before thread 0 can proceed, would be compromised, potentially leading to interleaved outputs or a sequence not anticipated or desired by the synchronization design .
The synchronized keyword in Java's PrintTable class ensures that only one thread can access the synchronized method at any given time. It locks the object for any thread attempting to access the method, blocking other threads until the lock is released. This control prevents thread interference and memory consistency errors by maintaining an orderly access, crucial for tasks involved in modifying shared resources, such as printing a table with sensitive timing and sequence dependence .
The Thread.sleep() method is used within MyThread's run method to introduce a delay between executions of sequential operations within the for loop. This planned pausing simulates real-world timing inconsistencies and helps avoid burdening the CPU continuous execution, providing a controlled and observable output. In the PrintTable's context, it ensures that threads pause momentarily between printing each line of the table, resulting in a spaced output appearance over time .
In the MyThread class, method overriding is used to provide a specific implementation of the run() method inherited from the Thread class. The significance lies in enabling polymorphic behavior where the start() method, called on a MyThread instance, executes the customized run() method, thus executing the specific sequence of operations defined for the MyThread object. This redefinition allows for each thread instance to perform actions distinct to its context, which in this case involves calls to the PrintTable's synchronized method using different integer arguments .
The usage of notify() in synchronized blocks influences thread access to shared resources by signaling one of the waiting threads to resume execution. Once a thread finishes its task and calls notify(), it effectively releases the lock, allowing another waiting thread to acquire it and access the shared resources. This coordination ensures orderly and regulated access to shared resources, as demonstrated when thread 1 calls notify() after its execution, which permits thread 0 to acquire the lock and proceed .
The concept of a shared resource is demonstrated in the Test1 class through the PrintTable instance passed to both MyThread objects. This single instance acts as a shared resource between threads, highlighting the significance of synchronization in managing concurrent access to shared data. This setup exemplifies real-world scenarios where multiple threads must utilize a common object safely and efficiently, necessitating techniques that control and coordinate access to prevent collisions and data inconsistencies in shared resources .
Choosing to extend the Thread class over implementing the Runnable interface is usually based on specific requirements. When extending the Thread class, a direct subclass is created, allowing the programmer to override thread behavior, which can be beneficial if there is a need to modify or enhance the threading mechanisms. Additionally, it simplifies thread initiation by calling start() on an instance of the class directly. However, this method inherits from one class only, limiting inheritance capabilities, unlike the Runnable interface, which allows for multiple inheritance paths. The Test1 example extends the Thread class potentially for ease of implementation and direct control over the thread behavior .
Synchronized blocks are generally preferred over synchronized methods because they allow for more fine-grained control over synchronization. This approach locks only the critical section of the code needed to be executed by a single thread at a time, improving performance and reducing overhead by allowing other threads to execute non-critical code outside the synchronized block. In contrast, synchronizing an entire method locks the whole method, potentially leading to thread contention and decreased efficiency .