1. Introduction
In this tutorial, we’re going to have a look at how we can print even and odd numbers using two threads.
The goal is to print the numbers in order, while one thread only prints the even numbers and the other thread only prints the odd numbers. We’ll be using the concepts of thread synchronization and inter-thread communication to solve the problem.
2. Threads in Java
Threads are lightweight processes which can execute concurrently. Concurrent execution of multiple threads can be good regarding performance and CPU utilization since we can work on more than one task at once through different threads running in parallel.
More information about threads in Java can be found in this article.
In Java, we can create a thread by either extending the Thread class or by implementing the Runnable interface. In both the cases, we override the run method and write the implementation of the thread in it.
More information on how to use these methods to create a thread can be found here.
3. Thread Synchronization
In a multi-threaded environment, it is possible that 2 or more threads are accessing the same resource at around the same time. This can be fatal and lead to erroneous results. To prevent this, we need to make sure that only one thread accesses the resource at a given point of time.
We can achieve this using thread synchronization.
In Java, we can mark a method or block as synchronized, which means that only one thread will be able to enter that method or block at a given point of time.
More details on thread synchronization in Java can be found over here.
4. Inter-Thread Communication
Inter-thread communication allows synchronized threads to communicate with each other using a set of methods.
The methods used are wait, notify, and notifyAll, which are all inherited from the Object class.
Wait() causes the current thread to wait indefinitely until some other thread calls notify() or notifyAll() on the same object. We can call notify() to waking up threads that are waiting for access to this object’s monitor.
More details about the working of these methods can be found here.
5. Printing Odd and Even Numbers Alternatively
5.1. Using wait() and notify()
We will use the discussed concepts of synchronization and inter-thread communication to print odd and even numbers in ascending order using two different threads.
In the first step, we’ll implement the Runnable interface to define the logic of both threads. In the run method, we check if the number is even or odd.
If the number is even, we call the printEven method of the Printer class, else we call the printOdd method:
class TaskEvenOdd implements Runnable {
private int max;
private Printer print;
private boolean isEvenNumber;
// standard constructors
@Override
public void run() {
int number = isEvenNumber ? 2 : 1;
while (number <= max) {
if (isEvenNumber) {
print.printEven(number);
} else {
print.printOdd(number);
}
number += 2;
}
}
}
We define the Printer class as follows:
class Printer {
private volatile boolean isOdd;
synchronized void printEven(int number) {
while (!isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = false;
notify();
}
synchronized void printOdd(int number) {
while (isOdd) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println(Thread.currentThread().getName() + ":" + number);
isOdd = true;
notify();
}
}
In the main method, we use the defined class to create two threads. We create an object of the Printer class and pass it as the parameter to the TaskEvenOdd constructor:
public static void main(String... args) {
Printer print = new Printer();
Thread t1 = new Thread(new TaskEvenOdd(print, 10, false),"Odd");
Thread t2 = new Thread(new TaskEvenOdd(print, 10, true),"Even");
t1.start();
t2.start();
}
The first thread will be the odd thread, hence we pass false as the value of the parameter isEvenNumber. For the second thread, we pass true instead. We set the maxValue to 10 for both threads, so that only the numbers from 1 through 10 are printed.
We then start both the threads by calling the start() method. This will invoke the run() method of both threads as defined above wherein we check if the number is odd or even and print them.
When the odd thread starts running, the value of the variable number will be 1. Since it is less than the maxValue and the flag isEvenNumber is false, printOdd() is called. In the method, we check if the flag isOdd is true and while it is true we call wait(). Since isOdd is false initially, wait() is not called, and the value is printed.
We then set the value of isOdd to true, so that the odd thread goes into the wait state and call notify() to wake up the even thread. The even thread then wakes up and prints the even number since the odd flag is false. It then calls notify() to wake up the odd thread.
The same process is carried out until the value of the variable number is greater than the maxValue.
5.2. Using Semaphores
A semaphore controls access to a shared resource through the use of a counter. If the counter is greater than zero, then access is allowed. If it is zero, then access is denied.
Java provides the Semaphore class in the java.util.concurrent package and we can use it to implement the explained mechanism. More details about semaphores can be found here.
We create two threads, an odd thread, and an even thread. The odd thread would print the odd numbers starting from 1, and the even thread will print the even numbers starting from 2.
Both the threads have an object of the SharedPrinter class. The SharedPrinter class will have two semaphores, semOdd and semEven which will have 1 and 0 permits to start with. This will ensure that odd number gets printed first.
We have two methods printEvenNum() and printOddNum(). The odd thread calls the printOddNum() method and the even thread calls the printEvenNum() method.
To print an odd number, the acquire() method is called on semOdd, and since the initial permit is 1, it acquires the access successfully, prints the odd number and calls release() on semEven.
Calling release() will increment the permit by 1 for semEven, and the even thread can then successfully acquire the access and print the even number.
This is the code for the workflow described above:
public static void main(String[] args) {
SharedPrinter sp = new SharedPrinter();
Thread odd = new Thread(new Odd(sp, 10),"Odd");
Thread even = new Thread(new Even(sp, 10),"Even");
odd.start();
even.start();
}
class SharedPrinter {
private Semaphore semEven = new Semaphore(0);
private Semaphore semOdd = new Semaphore(1);
void printEvenNum(int num) {
try {
semEven.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semOdd.release();
}
void printOddNum(int num) {
try {
semOdd.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + num);
semEven.release();
}
}
class Even implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructor
@Override
public void run() {
for (int i = 2; i <= max; i = i + 2) {
sp.printEvenNum(i);
}
}
}
class Odd implements Runnable {
private SharedPrinter sp;
private int max;
// standard constructors
@Override
public void run() {
for (int i = 1; i <= max; i = i + 2) {
sp.printOddNum(i);
}
}
}
6. Conclusion
In this tutorial, we had a look at how we can print odd and even numbers alternatively using two threads in Java. We had a look at two methods to achieve the same results: using wait() and notify() and using a Semaphore.
And, as always, the full working code is available over on GitHub.