eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

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.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook – Java Concurrency – NPI (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook Jackson – NPI EA – 3 (cat = Jackson)