Core Java

Multithreading in Java Tutorial

In this post, we feature a comprehensive Multithreading in Java Tutorial. Multithreading is the ability of a program to manage its use by more than one user and even to manage multiple requests by the same user. In the Java programming language, concurrent programming is the execution of processes and threads. Java has supported java.lang.Thread since JDK 1.0. The java.util.concurrent API is released in Java 5. Java collections – Vector, Stack and HashTable are thread-safe. Java provides synchronized functionality to support thread-safety on collections such as Collections.SynchronizedCollection() and Collections.SynchronizedMap().

You can also check this tutorial in the following video:

Java Multithreading Tutorial – video

1. Process and Thread

A process is an execution of a program and a thread is a single execution of work within the process. A process can contain multiple threads. A thread is also known as a lightweight process.

Multithreading in Java - process in java
Figure 1 Process in Java

In Java, a process is run independently from other processes in a JVM and threads in a JVM share the heap belonging to that process. That is why several threads may access the same object in the heap. Threads share the heap and have their own stack space. Therefore, an invocation of a method and its local variables are kept thread safe from other threads, while heap is not thread safe and must be synchronized for multithreaded programs.

2. Life Cycle of a Thread

A thread can be in different states in its life cycle. The diagram below displays different states of a thread which are start, run, sleep/wait/block and done.

Multithreading in Java - thread lifecycle
Figure 2 Thread Lifecycle
  • New: When a thread is created, it is in the new state.
  • Runnable: A thread is waiting for its turn to be picked for execution. The thread is selected by the thread scheduler based on thread priorities. The start() methods registers a thread in a thread scheduler.
  • Running: The processor is executing the thread. The thread runs until it becomes blocked or give up its turn with Thread.yield(). Due to overhead of context switching, yield() should not be used very frequently. The wait(), sleep(), and join() methods make thread leave the running state.
  • Waiting: A thread waits for another thread to perform a task.
  • Sleeping: Java threads are forced to sleep (suspended) with this overloaded method: Thread.sleep(milliseconds), Thread.sleep(milliseconds, nanoseconds).
  • Blocked on I/O: A thread is blocked on some external I/O processing to finish. Thread will move to Runnable after I/O condition like reading bytes of data etc changes.
  • Blocked on synchronization: Thread will move to Runnable when a lock is acquired.
  • Terminated: The thread is finished its work.

3. Technologies Used

The example code in this article was built and run using:

  • Java 11
  • Maven 3.3.9
  • Eclipse Oxygen
  • Logback 1.2.3
  • Junit 4.12

4. Maven Project

In this step, I will create a Maven project to demonstrate multi-threading in Java.

4.1 Dependencies

Add Logback and Junit libraries to the pom.xml.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>java-multithreads-demo</groupId>
	<artifactId>java-multithreads-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.0</version>
				<configuration>
					<release>11</release>
				</configuration>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-access</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.2.3</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
			<version>1.2.3</version>
		</dependency>
	</dependencies>
</project>

4.2 CommonUtil

In this step, I will create a CommonUtil class which holds the constants – THREAD_STARTED, THREAD_FINISHED – and THREAD_STATE and two methods – fiveSecondsProcess() and waitforThread().

CommonUtil.java

package jcg.zheng.multithread.demo;

import java.util.concurrent.TimeUnit;

public class CommonUtil {

	public static final String THREAD_FINISHED = " Thread finished";
	public static final String THREAD_STARTED = " Thread started";
	public static final String THREAD_STATE = "Thread state = ";

	public static void fiveSecondsProcess() {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void waitForThread(Thread thread) {
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

5. Create a Thread

Java Thread has nine constructors to create a new thread. We categorize into two ways:

  • Extends Thread class
  • Implements Runnable interface

5.1 Extends Thread Class

The Thread class itself implements the Runnable interface. When a class extends Thread, it should override the run() method and provide its own implementation of run().

The start() method in the Thread class starts the execution of a thread. The thread will be alive until the execution of the run method is finished.

In this step, I will create a ThreadExample class which extends from Thread and have two run methods:

  • run() – annotates with @Override, it will be called by the Thread’s start() method.
  • run(String msg) – it is a normal class method. It will be called when invoking it.

ThreadExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadExample extends Thread {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	@Override
	public void run() {
		logger.info(CommonUtil.THREAD_STARTED);
		CommonUtil.fiveSecondsProcess();
		logger.info(CommonUtil.THREAD_FINISHED);
	}

	public void run(String msg) {
		logger.info(" ** " + msg);
	}

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

		ThreadExample thread = new ThreadExample();
		thread.run("Mary : Hello !");
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());

		thread.start();
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());

		if (args.length == 1 && args[0].equalsIgnoreCase("wait")) {
			System.out.println("Wait!");
			CommonUtil.waitForThread(thread);
		}

		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
	}
}

Execute it as a Java application and capture the output here.

Output

C:\Users\aa00765\Desktop\Design_diagrams>java -jar TheadExample.jar wait
main Thread started
17:12:12.040 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
mainThread state = NEW
mainThread state = RUNNABLE
Wait!
17:12:12.048 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:12:17.051 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
mainThread state = TERMINATED
main Thread finished

C:\Users\aa00765\Desktop\Design_diagrams>java -jar TheadExample.jar
main Thread started
17:12:20.402 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
mainThread state = NEW
mainThread state = RUNNABLE
mainThread state = RUNNABLE
17:12:20.410 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
main Thread finished
17:12:25.416 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished

C:\Users\aa00765\Desktop\Design_diagrams>

5.2 Implements Runnable

Similar to the previous implementation of a thread, when a class implements Runnable interface, it should provide its own implementation of run(). Java 5 marks the Runnable interface with @FunctionalInterface, so we can use Java 8 lambda to create a new thread with a Runnable.

In this step, I will create a RunnableThreadExample class which implements the Runnable interface.

RunnableThreadExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class RunnableThreadExample implements Runnable {

 private Logger logger = LoggerFactory.getLogger(this.getClass());

 @Override
 public void run() {
 logger.info(CommonUtil.THREAD_STARTED);
 CommonUtil.fiveSecondsProcess();
 logger.info(CommonUtil.THREAD_FINISHED);
 }

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 Thread thread = new Thread(new RunnableThreadExample());
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());

 thread.start();
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STATE + thread.getState());
 if (args.length == 1 && args[0].equalsIgnoreCase("wait")) {
 System.out.println("Wait!");
 CommonUtil.waitForThread(thread);
 }
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
 }

}

Execute it as a Java application and capture the output here.

Output

main Thread started
mainThread state = NEW
mainThread state = RUNNABLE
main Thread finished
17:15:08.822 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:15:13.844 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished

main Thread started
mainThread state = NEW
mainThread state = RUNNABLE
Wait!
17:15:41.740 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:15:46.751 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
main Thread finished

5.3 ThreadTestBase

In this step, I will create a ThreadTestBase which test a thread’s creation, execution, and its state.

ThreadTestBase.java

package jcg.zheng.multithread.demo.thread;

import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import org.junit.Test;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadTestBase {

	Thread thread;

	@Test
	public void create_start_finish() {
		assertEquals(State.NEW, thread.getState());

		assertEquals(State.NEW, thread.getState());

		thread.start();
		assertEquals(State.RUNNABLE, thread.getState());

		CommonUtil.waitForThread(thread);

		assertEquals(State.TERMINATED, thread.getState());
	}

	@Test(expected = IllegalThreadStateException.class)
	public void throw_exception_start_twice() {
		thread.start();
		thread.start();
	}

}

As you have seen in the create_start_finish method, the newly created thread is in the NEW state, then changes to RUNNABLE by the start method, finally, it’s in TERMINATED after it’s completed.

As you have seen in the throw_exception_start_twice method, it throws an IllegalThreadStateExecption when invoking start() twice.

5.4 ThreadExampleTest

In this step, I will create a ThreadExampleTest. At the setup method, it creates a Thread instance from ThreadExample.

ThreadExampleTest.java

package jcg.zheng.multithread.demo.thread;

import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import org.junit.Before;

public class ThreadExampleTest extends ThreadTestBase{

	@Before
	public void setup() {
		thread = new ThreadExample();
		assertEquals(State.NEW, thread.getState());
		
		((ThreadExample)thread).run("Mary : Hello !");
		assertEquals(State.NEW, thread.getState());
	}

}

Execute mvn test -Dtest=ThreadExampleTest and capture the output here.

Output

Running jcg.zheng.multithread.demo.thread.ThreadExampleTest
17:19:25.769 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
17:19:25.781 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:19:30.796 [Thread-0] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:19:30.802 [main] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  ** Mary : Hello !
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.38 sec
17:19:30.838 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  16.388 s
[INFO] Finished at: 2019-08-12T17:19:31-05:00
[INFO] ------------------------------------------------------------------------

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo>

5.5 RunnableThreadExampleTest

In this step, I will a RunnableThreadExampleTest with two methods:

  • setup – create a Thread instance from RunnableThreadExample
  • createWithLambda – create a Thread instance with Java 8 Lamdba syntax

RunnableThreadExampleTest.java

package jcg.zheng.multithread.demo.thread;

import static org.junit.Assert.assertEquals;

import java.lang.Thread.State;

import org.junit.Before;
import org.junit.Test;

public class RunnableThreadExampleTest extends ThreadTestBase {

	@Before
	public void setup() {
		thread = new Thread(new RunnableThreadExample());
	}

	@Test
	public void createWithLambda() {
		thread = new Thread(() -> {
			System.out.println(("Hello from Lambda!"));
		});

		assertEquals(State.NEW, thread.getState());
		thread.start();
		assertEquals(State.RUNNABLE, thread.getState());
	}

}

Execute mvn test -Dtest=RunnableThreadExampleTest and capture the output here.

Output

Running jcg.zheng.multithread.demo.thread.RunnableThreadExampleTest
Hello from Lambda!
17:22:51.231 [Thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:22:56.238 [Thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.437 sec

Results :

Tests run: 3, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.272 s
[INFO] Finished at: 2019-08-12T17:22:56-05:00
[INFO] ------------------------------------------------------------------------

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo>

6. Thread Details

6.1 Thread Priority

Every thread has a priority that helps the program to determine the order which threads are scheduled. Thread priority is between MIN_PRIORITY (a constant of 1) and MAX_PRIORITY (a constant of 10). The default priority of a thread is NORM_PRIORITY (a constant of 5). Threads with higher priority are executed before threads with lower priority. However, thread priorities do not guarantee the order in which threads execute next and it is also platform dependent.

ThreadPriorityExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadPriorityExample extends Thread {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	public void run() {
		logger.info(CommonUtil.THREAD_STARTED);
		logger.info("Running Thread Priority: " + Thread.currentThread().getPriority());
		CommonUtil.fiveSecondsProcess();
		logger.info(CommonUtil.THREAD_FINISHED);
	}

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

		/* Thread Priority */
		ThreadPriorityExample minThread = new ThreadPriorityExample();
		minThread.setName("Thread1_MIN_PRIORITY");
		minThread.setPriority(Thread.MIN_PRIORITY);

		ThreadPriorityExample maxThread = new ThreadPriorityExample();
		maxThread.setName("Thread2_MAX_PRIORITY");
		maxThread.setPriority(Thread.MAX_PRIORITY);

		ThreadPriorityExample thread3 = new ThreadPriorityExample();
		thread3.setName("Thread3");

		System.out.println(Thread.currentThread().getName() + " starts with min, max, default priority order");
		minThread.start();
		maxThread.start();
		thread3.start();

		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
	}

}

Execute it as a Java application and capture the output here.

Output

main Thread started
main starts with min, max, default priority order
main Thread finished
17:24:59.573 [Thread2_MAX_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread started
17:24:59.580 [Thread2_MAX_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample - Running Thread Priority: 10
17:24:59.573 [Thread3] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread started
17:24:59.593 [Thread3] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample - Running Thread Priority: 5
17:24:59.573 [Thread1_MIN_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread started
17:24:59.594 [Thread1_MIN_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample - Running Thread Priority: 1
17:25:04.584 [Thread2_MAX_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread finished
17:25:04.594 [Thread3] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread finished
17:25:04.594 [Thread1_MIN_PRIORITY] INFO jcg.zheng.multithread.demo.thread.ThreadPriorityExample -  Thread finished

6.2 Thread Factory

The ThreadFactory Interface defines a newThread(Runnable r) method to create a thread on demand.

In this step, I will create a Thread from ThreadFactory.

ThreadFactoryExample.java

package jcg.zheng.multithread.demo.thread;

import java.util.concurrent.ThreadFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadFactoryExample implements ThreadFactory {

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 ThreadFactoryExample tFactory = new ThreadFactoryExample("MZhengThreadFactory");
 for (int i = 0; i < 5; i++) {
 Thread thread = tFactory.newThread(new ThreadExample());
 thread.start();
 }
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
 }

 private int threadId;

 private String threadName;

 public ThreadFactoryExample(String name) {
 threadId = 1;
 this.threadName = name;
 }

 @Override
 public Thread newThread(Runnable r) {
 Thread thread = new Thread(r, threadName + "-Thread_" + threadId);
 threadId++;
 return thread;
 }

}

Execute it as a Java application and capture the output here.

Output

main Thread started
main Thread finished
17:26:52.681 [MZhengThreadFactory-Thread_2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.684 [MZhengThreadFactory-Thread_3] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.685 [MZhengThreadFactory-Thread_1] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.681 [MZhengThreadFactory-Thread_5] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:52.684 [MZhengThreadFactory-Thread_4] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
17:26:57.724 [MZhengThreadFactory-Thread_1] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.724 [MZhengThreadFactory-Thread_2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.724 [MZhengThreadFactory-Thread_3] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.725 [MZhengThreadFactory-Thread_4] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished
17:26:57.725 [MZhengThreadFactory-Thread_5] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished

6.3 Executor Service

ExecutorService provides an indirect way to create a thread. It manages a thread pool, all threads of the internal pool will be reused.

In this step, I will demonstrate how to get a thread from ExecutorService.

ExecutorServiceExample.java

package jcg.zheng.multithread.demo.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import jcg.zheng.multithread.demo.CommonUtil;

public class ExecutorServiceExample {

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 ExecutorService service = Executors.newFixedThreadPool(5);

 for (int i = 0; i < 5; i++) {
 service.submit(new RunnableThreadExample());
 }

 service.submit(() -> {
 System.out.println(Thread.currentThread().getName() + " is reused");
 });

 try {

 // Executor must be stopped explicitly otherwise it keeps listens for new
 // tasks
 service.shutdown();
 service.awaitTermination(10l, TimeUnit.SECONDS);

 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {

 System.out.println(Thread.currentThread().getName() + " isTerminated = " + service.isTerminated());
 service.shutdownNow();
 }
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);

 }

}

Execute it as a Java application and capture the output here.

Output

main Thread started
17:28:15.344 [pool-1-thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.345 [pool-1-thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.344 [pool-1-thread-4] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.345 [pool-1-thread-3] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:15.344 [pool-1-thread-5] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
17:28:20.357 [pool-1-thread-2] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-3] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-5] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
17:28:20.357 [pool-1-thread-4] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
pool-1-thread-4 is reused
main isTerminated = true
main Thread finished

As you have seen in this example, it created a thread-pool with five threads. The thread-4 is reused for the sixth request.

6.4 Thread Notify and Wait

In this step, I will invoke the notify() and wait() methods to wake up and pause the current thread.

First, I will create two synchronized methods:

  • printEven – it notifies the current thread if the number is even and pauses the current thread if the number is odd.
  • printOdd – it notifies the current thread if the number is odd and pauses the current thread if the number is even.

Second, I will create a ThreadPrintOdd class which extends from Thread and invokes the printOdd() in the run() method.

Third, I will create a Runnable interface via Java 8 lambda syntax which invokes printEven() method.

Finally, I will create a main application which creates oddThread and evenThreadJdk8 and starts both thread. As the output shows, these two threads will take a turn to pause and execute.

ThreadNotifyWaitExample.java

package jcg.zheng.multithread.demo.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcg.zheng.multithread.demo.CommonUtil;

public class ThreadNotifyWaitExample extends Thread {

 private static final class ThreadPrintOdd extends Thread {
 private final ThreadNotifyWaitExample tExample;

 private ThreadPrintOdd(ThreadNotifyWaitExample tExample) {
 this.tExample = tExample;
 }

 @Override
 public void run() {
 try {
 tExample.printOdd();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }

 public static void main(String[] args) {
 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

 ThreadNotifyWaitExample tExample = new ThreadNotifyWaitExample();

 Thread oddThread = new ThreadPrintOdd(tExample);
 Thread evenThreadJdk8 = new Thread(printEven(tExample));

 oddThread.start();
 evenThreadJdk8.start();

 System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);

 }

 private static Runnable printEven(ThreadNotifyWaitExample tExample) {
 return () -> {
 try {
 tExample.printEven();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 };
 }

 private Logger logger = LoggerFactory.getLogger(this.getClass());

 public synchronized void printEven() throws InterruptedException {
 for (int i = 0; i < 10; i++) {
 if (i % 2 == 0) {
 this.notify();
 logger.info("Even: " + i);
 } else {
 this.wait();
 }
 }
 }

 public synchronized void printOdd() throws InterruptedException {
 for (int i = 0; i < 10; i++) {
 if (i % 2 == 0) {
 this.wait();
 } else {
 this.notify();
 logger.info("Odd: " + i);
 }
 }
 }

}

Execute it as a Java application and capture the output here.

Output

main Thread started
main Thread finished
17:29:19.232 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 0
17:29:19.241 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 1
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 2
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 3
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 4
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 5
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 6
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 7
17:29:19.242 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Even: 8
17:29:19.242 [Thread-1] INFO jcg.zheng.multithread.demo.thread.ThreadNotifyWaitExample - Odd: 9

7. Multithreading in Java

In the step, I will create a multithreading application. It has three threads:

  • thread1 – instance of RunnableTheadExample
  • thread2 – instance of RunnableThreadExample
  • thread3 – instance of ThreadExample

After created three threads, thread1 and thread2 are started, then,CommonUtil.waitForThread(thread1) is called to pause the current until thread1 is finished. After both thread1 and thread2 completes, thread3 will be executed. Finally, the main thread exits before thread3 completes.

MultithreadsApp.java

package jcg.zheng.multithread.demo.thread;

import jcg.zheng.multithread.demo.CommonUtil;

public class MultiThreadsApp extends Thread {

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_STARTED);

		Thread thread1 = new Thread(new RunnableThreadExample());
		Thread thread2 = new Thread(new RunnableThreadExample());
		ThreadExample thread3 = new ThreadExample();

		thread1.start();
		thread2.start();

		CommonUtil.waitForThread(thread1);
		CommonUtil.waitForThread(thread2);

		thread3.start();

		System.out.println(Thread.currentThread().getName() + CommonUtil.THREAD_FINISHED);
	}

}

Execute it as a Java application and capture the output here.

Output

main Thread started
18:12:56.367 [Thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
18:12:56.367 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread started
18:13:01.384 [Thread-1] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
18:13:01.384 [Thread-0] INFO jcg.zheng.multithread.demo.thread.RunnableThreadExample -  Thread finished
main Thread finished
18:13:01.386 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread started
18:13:06.386 [Thread-2] INFO jcg.zheng.multithread.demo.thread.ThreadExample -  Thread finished

8. Synchronization

Synchronization is about to control the access of threads on shared resources in the program. In Java, each object has a lock. A thread can acquire the lock for an object by using synchronized keyword.

The synchronized keyword can be implemented in method level or block level. The block level is more efficient in comparison to method level because it does not lock the whole method.

8.1 Race Condition

In this step, I will create a simple Java application to demonstrate the race condition in a concurrent multithreading application.

The RaceConditionExample class has a mutable member – count. In the raceConditionDemo method, It will create a thread-pool with 5 threads and submit 10000 times. So it should print out 10000.

As you will see in the output, it prints as 9987 due to race condition. You will get unpredictable number which closes to 10000 for each execution.

RaceConditionExample.java

package jcg.zheng.multithread.demo.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class RaceConditionExample {

	private int count = 0;

	public int getCount() {
		return count;
	}

	AtomicInteger atomicCount = new AtomicInteger();
	public int getCountViaAtomicInteger() {
		count = atomicCount.incrementAndGet();
		return count;
	}

	public void increment() {
		count++;
	}

	public synchronized void incrementSyncMethod() {
		count++;
	}

	public void incrementSyncBlock() {
		synchronized (this) {
			count++;
		}
	}

	// mutual exclusion, same as synchronized
	ReentrantLock lock = new ReentrantLock();
	public void incrementLock() {
		lock.lock();
		try {
			count++;
		} finally {
			lock.unlock();
		}
	}

	public void raceConditionDemo(String type) {
		ExecutorService executor = Executors.newFixedThreadPool(5);
		for (int i = 0; i < 10000; i++) {
			switch (type) {
			case "RaceCondition":
				executor.submit(this::increment);
				break;
			case "Lock":
				executor.submit(this::incrementLock);
				break;
			case "SynchronizedBlock":
				executor.submit(this::incrementSyncBlock);
				break;
			case "SynchronizedMethod":
				executor.submit(this::incrementSyncMethod);
				break;
			case "AtomicInteger":
				executor.submit(this::getCountViaAtomicInteger);
				break;
			}
		}

		try {
			executor.awaitTermination(10l, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		executor.shutdown();
	}

	public static void main(String[] args) {
		RaceConditionExample rE = new RaceConditionExample();
		rE.raceConditionDemo(args[0]);
		System.out.println("Count = " + rE.getCount());
	}
}

Execute it as a Java application and capture the output here.

Output

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample RaceCondition
Count = 9987

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.2 Synchronized Block

When multiple threads access the same mutable objects, we should synchronize the mutable object to avoid the incorrect data due to race condition error.

Please reference the incrementSyncBlock method.

Execute it as a Java application and capture the output here.

Output

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample SynchronizedBlock
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.3 Synchronized Method

The xx method is marked with synchronized keyword. I will demonstrate how to use it to avoid the race condition

Please reference the incrementSyncMethod method

Execute it as a Java application and capture the output here.

Output

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample SynchronizedMethod
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.4 Lock

In this step, I will demonstrate how to use ReentrantLock to avoid the race condition.

Execute it as a Java application and capture the output here.

Output

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample Lock
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

8.5 AtomicInteger

In this step, I will demonstrate how to use the AtomicInteger class to avoid race condition.

Execute it as a Java application and capture the output here.

Output

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>java jcg.zheng.multithread.demo.thread.RaceConditionExample AtomicInteger
Count = 10000

C:\MaryZheng\Workspaces\jdk12\java-multithreads-demo\target\classes>

9. Summary

In this tutorial, I demonstrated thread creation, thread state, thread priority, thread factory, thread pool, and synchronization. The java.util.concurrent API contains high-level utilities that are mainly used in concurrent programming. Here are the pros and cons of Multi-threading.

Pros:

  • Better use of system resources
  • Parallel execution of tasks and thus less execution time
  • Enhanced performance on multi-processor machines
  • Improved GUI responsiveness
  • Independent threads (don’t impact other threads of the same process if an exception occurs)

Cons:

  • Complexity of the code
  • Synchronization of shared resources (objects, data) is CPU/memory intensive
  • Debugging is hard because sometimes you can’t predict the results
  • Increased potential for deadlock occurrence
  • “Starvation” some of the threads may not be served with due to poor design

10. Download the Source Code

This was a tutorial for Java multithreading tutorial.

Download
You can download the full source code of this example here: Multithreading in Java Tutorial

Multithreading in Java Tutorial was last updated on Aug 14th, 2019

Mary Zheng

Mary has graduated from Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She works as a senior Software Engineer in the telecommunications sector where she acts as a leader and works with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

8 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
mayur kohli
6 years ago

Nice post!

After understanding the basics of Java multithreading, I recommend to check the Executors framework. Together with various built in classes, making complex multithreaded applications is simpler and cleaner than using Threads directly.

Rajisha
Rajisha
6 years ago

Can you please help me to find out the answer of the below question using threads??

The O/P should become

ODD : 1

EVEN : 2

EVEN : 4

ODD : 3

. .

ODD : 9

ODD : 11

ODD : 13

EVEN : 6

. .

EVEN : 20

ODD : 15

. .

ODD : 19

Atish Kumar
Atish Kumar
6 years ago
Reply to  Rajisha

public class ThreadEvenOdd extends Thread{ public ThreadEvenOdd() { } public synchronized void printOdd() throws InterruptedException{ for(int i=1;i<10;i=i+1){ if(i%2==0){ this.wait(); } else{ this.notify(); System.out.println("odd"+" " +i); } } } public synchronized void printEven() throws InterruptedException{ for(int i=2;i<10;i=i+1){ if(i%2==0){ this.notify(); System.out.println("even"+" "+i); } else{ this.wait(); } } } public static void main(String[] args) { ThreadEvenOdd even=new ThreadEvenOdd(); Thread thread=new Thread() { @Override public void run() { try { even.printOdd(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; Thread thread1=new Thread() { @Override public void run() { try { even.printEven(); } catch (InterruptedException e) { // TODO… Read more »

Teshome
Teshome
6 years ago

I’m doing core java programming language I/O streaming, swing and AWT,jDBC, RMI, javanetwork programming, servlets, JSP shell and python programming any body who can help me

ganesh kumar
6 years ago
Reply to  Teshome

thanks for this great information on multi threading in java. I have found this really helpful..
Keep Posting here Multithreading In Java

Sujata
5 years ago

Java provides multithreading support with the Thread class and an application can create multiple threads executing concurrently. Thanks for sharing this information.

collegedone
4 years ago

Thanks for this great information on multi threading in java.

rohit aggarwal
4 years ago

thanks for the information

Back to top button