CS F213 Objected Oriented Programming Labsheet 7
Spring 2024
Java Threads
Threads allows a program to operate more efficiently by doing multiple things at the same time. Threads can be used to perform complicated tasks in the background without interrupting the main program.
Threads exist in several states. Here is a general description. A thread can be running
. It can be ready to run as soon as it gets CPU time. A running thread can be suspended
, which temporarily halts its activity. A suspended thread can then be resumed
, allowing it to pick up where it left off. A thread can be blocked
when waiting for a resource. At any time, a thread can be terminated
, which halts its execution immediately. Once terminated, a thread cannot be resumed.
Creating a Thread
There are two ways to create a thread.
It can be created by extending the
Thread
class and overriding itsrun()
method:
Another way to create a thread is to implement the
Runnable interface
:
Inside run( ), you will define the code that constitutes the new thread. It is important to understand that run( ) can call other methods, use other classes, and declare variables, just like the main thread can. The only difference is that run( ) establishes the entry point for another, concurrent thread of execution within your program. This thread will end when run( ) returns.
Running Threads
If the class extends the
Thread
class, the thread can be run by creating an instance of the class and call itsstart()
method:
If the class implements the
Runnable
interface, the thread can be run by passing an instance of the class to a Thread object's constructor and then calling the thread'sstart()
method:
Output
The output of this code is non-deterministic due to the concurrent execution of the main thread and the new thread created by extending the Thread class. The two threads run independently
, and the order in which they execute their statements is not guaranteed.
If the main thread gets executed before the new thread
If the new thread gets executed before the main thread
The actual order of execution depends on the thread scheduler
and how the operating system manages the execution of threads. Therefore, you might observe either of the two possible outcomes
Differences between "extending" and "implementing" Threads The major difference is that when a class extends the Thread class, you cannot extend any other class, but by implementing the Runnable interface, it is possible to extend from another class as well, like:
class MyClass extends OtherClass implements Runnable.
Concurrency problems
Because threads run at the same time as other parts of the program, there is no way to know in which order the code will run. When the threads and main program are reading and writing the same variables, the values are unpredictable. The problems that result from this are called concurrency problems.
Often you will want the main thread to finish last. This can be accomplished by calling sleep()
within main(), with a long enough delay to ensure that all child threads terminate prior to the main thread. However, this is hardly a satisfactory solution, and it also raises a larger question: How can one thread know when another thread has ended? Two ways exist to determine whether a thread has finished.
isAlive() and join()
The isAlive()
method returns true if the thread upon which it is called is still running. It returns false otherwise.
The join()
method waits until the thread on which it is called terminates. Its name comes from the concept of the calling thread waiting until the specified thread joins it. Additional forms of join() allow you to specify a maximum amount of time that you want to wait for the specified thread to terminate
Output
Output
Synchronization
Multi-threaded programs may often come to a situation where multiple threads try to access the same resources and finally produce erroneous and unforeseen results. Java Synchronization is used to make sure by some synchronization method that only one thread can access the resource at a given point in time.
Java provides a way of creating threads and synchronizing their tasks using synchronized blocks
. A synchronized block in Java is synchronized on some object. All synchronized blocks synchronize on the same object and can only have one thread executed inside them at a time. All other threads attempting to enter the synchronized block are blocked until the thread inside the synchronized block exits the block.
Without Synchronized
This may or may not print counter value in sequence and every time we run it, it produces a different result based on CPU availability to a thread
If we change the run() method and synchronize it like this
Then the output becomes
This produces the same result every time you run this program
Interthread Communication
Java includes an elegant interprocess communication mechanism via the wait()
, notify()
, and notifyAll()
methods. These methods are implemented as final
methods in Object, so all classes have them. All three methods can be called only from within a synchronized
context.
wait()
tells the calling thread to give up the monitor and go to sleep until some other thread enters the same monitor and calls notify() or notifyAll()notify()
wakes up a thread that called wait( ) on the same object.notifyAll()
wakes up all the threads that called wait( ) on the same object. One of the threads will be granted access.
These methods are declared within Object, as shown here:
final void wait() throws InterruptedException final void notify() final void notify All()
Output
Obtaining a Thread’s State
A thread can exist in a number of different states. You can obtain the current state of a thread by calling the getState()
method defined by Thread.
It returns a value of type Thread.State that indicates the state of the thread at the time at which the call was made.
Values that can be returned by getState( ):
BLOCKED : A thread that has suspended execution because it is waiting to acquire a lock
NEW : A thread that has not begun execution.
RUNNABLE : A thread that either is currently executing or will execute when it gains access to the CPU.
TERMINATED : A thread that has completed execution.
TIMED_WAITING : A thread that has suspended execution for a specified period of time, such as when it has called sleep(). This state is also entered when a timeout version of wait( ) or join( ) is called.
WAITING : A thread that has suspended execution because it is waiting for some action to occur. For example, it is waiting because of a call to a nontimeout version of wait( ) or join( ).
getState( ) is not intended to provide a means of synchronizing threads. It’s primarily used for debugging or for profiling a thread’s run-time characteristics.
Thead Priority
In Java, each thread is assigned a priority which affects the order in which it is scheduled for running. The threads are of the same priority by the Java Scheduler and therefore they share the processor on a First-Come-First-Serve basis.
Java permits us to set the priority of a thread using the setPriority()
as follows:
Where intNumber is an integer value to which the thread’s priority is set. The Thread class defines several priority constants:
MIN_PRIORITY = 1 NORM_PRIORITY = 5 MAX_PRIORITY = 10
Summary of Java Threads Methods
void
start()
It is used to start the execution of the thread.
void
run()
It is used to perform the main action for a thread.
static void
sleep()
It sleeps a thread for the specified amount of time.
static Thread
currentThread()
It returns a reference to the currently executing thread object.
void
join()
It waits for a thread to die.
int
getPriority()
It returns the priority of the thread.
void
setPriority()
It changes the priority of the thread.
String
getName()
It returns the name of the thread.
void
setName()
It changes the name of the thread.
Thread.State
getState()
It is used to return the state of the thread.
void
notify()
It is used to give the notification for only one thread which is waiting for a particular object.
void
notifyAll()
It is used to give the notification to all waiting threads of a particular object.
Exercises
Exercise 1
Create a program that simulates a shared resource among five
threads. The resource is a simple counter that starts from 0. Each thread should increment the counter 1000
times. Use synchronization
to ensure that the counter value is accurate, and print the final value of the counter.
Sample Output
you can use Thread.currentThread().getId()
to get the number of the currenly executed thread
Exercise 2
You are tasked with implementing a simple producer-consumer system using a shared array-based queue. The system should support multiple producers and multiple consumers. Each producer will produce integers sequentially (starting from 1), and consumers will consume these integers. The system should have a buffer (queue) of a specified size, and the producers and consumers should synchronize their access to the shared buffer.
Requirements:
Implement a
SharedQueue
class representing the shared buffer with the following methods:produce(int producerId)
: Produces integers sequentially (starting from 1) and adds them to the buffer. If the buffer is full, the producer should wait for space to become available.consume(int consumerId)
: Consumes integers from the buffer. If the buffer is empty, the consumer should wait for items to be produced.
Implement a
Producer
class that implements theRunnable
interface. Each producer should have a unique ID and continuously produce integers using theproduce
method of the shared buffer.Implement a
Consumer
class that also implements theRunnable
interface. Each consumer should have a unique ID and continuously consume integers using theconsume
method of the shared buffer.In the
main
method of theProducerConsumerExample
class:Create an instance of
SharedQueue
with a buffer size of 5.Create three producers and three consumers, each with unique IDs.
Start the producer and consumer threads.
Constraints:
The shared buffer (queue) should be implemented using a circular array.
The buffer size should be fixed at 5.
The producer and consumer threads should run indefinitely.
Note:
Use synchronization mechanisms to ensure mutual exclusion and coordination between producers and consumers.
The output should showcase the sequential production and consumption of integers by producers and consumers.
Sample Output
The template is given below for your convenience (You have to understand the whole code and complete the 11 commented parts to get the required output)
Exercise 3
You are tasked with creating a simple task scheduler that can execute tasks with different priorities. The program will have two types of tasks: high-priority and low-priority. High-priority tasks should be executed before low-priority tasks. Each task represents a thread in the program.
Create a class named Task
that implements the Runnable
interface. The Task class should have the following properties:
name: A string representing the name of the task. priority: An integer representing the priority of the task. (1 for high-priority, 2 for low-priority) executionTime: An integer representing the time it takes to execute the task.
Create another class named Main
that will manage the execution of tasks. The TaskScheduler class should have the following methods:
addTask(Task task): Adds a task to the scheduler. executeTasks(): Executes the tasks based on their priorities. printTaskStatus(): Prints the status of each task (e.g., running, waiting).
Use synchronized, wait, notify, setPriority, and getState to implement synchronization and priority handling.
The boiler code is given below, you are required to complete the parts given in comments
Sample Output
Last updated