Debugging concurrency issues or race conditions in your code is usually difficult. This post should give you a good start to debug multithreaded Java applications.
We will learn through an example. Here, I have written a multithreaded program to calculate this math problem:
100! + 100000!
Here is what is happening in the above code: We initialize, name, and start two threads - 'Thread 1' (to calculate 100!) and 'Thread 2' (to calculate 100000!). Then in the main() method, we call
thread1.join() so that the main thread won't execute further until 'Thread 1' returns. Similarly, we call
thread2.join() Use of Thread.join() method ensures that sum (on line 25) is not calculated until both the threads return, that is, addition is performed only after we get the factorials of 100 and 100000.
Let us explore the IntelliJ IDEA (v. 2019.2.2 (CE)) tools that I often use while debugging a multithreaded app.
The Debug tool window has Frame pane which consists of a drop down. It focuses on the thread which is currently paused because of the breakpoint and shows the call stack of that thread. In the image below, the breakpoint is in the main() method and the Frame is showing us the call stack for the main thread.
If you want to check call stack of other threads, you can select them from the drop down.
Thread pane shows all the threads that are currently active. Referring to the code above, I have added a breakpoint at
thread1.join()(on line 18). When the app pauses at that breakpoint, we should see at least three threads - 'main', 'Thread 1' and 'Thread 2' in this pane (check screenshot below). You can double click on each thread to observe their call stacks.
Assume that I am troubleshooting a bug in this program and I need to pause execution only for 'Thread 2' as soon as it starts running. This suggests that I need to add a breakpoint on the first line of FactorialCalculatingThread's run() method (line 39). But we will run into a problem - all the threads that encounter the breakpoint will be suspended. This includes 'Thread 1' and 'Thread 2' for our app. I don't want both the threads to pause. Can you think of any other approach?
We can use conditional breakpoint feature. Let us see how. After adding a breakpoint, right-click on it, check 'Suspend' and select 'Thread'. Then we add the condition as shown in the screenshot below. This condition ensures that the debugger would pause the current thread only if that thread's name is 'Thread 2':
Now, debug the program. When the app pauses, only 'Thread 2' is suspended. You can confirm that 'Thread 1' was executed and didn't get suspended through the following steps:
1.In the console, you can verify through the logs that 'Thread 1' ran and exited.
2.In the Threads pane, you can check that there is no 'Thread 1'
The way to configure conditional breakpoints may be different in different IDE versions. But the key idea is to be aware of these features and use them.