29
loading...
This website collects cookies to deliver better user experience
private static void computeCounter(){
Counter counter = new Counter() ;
Thread threadOne = new Thread(getRunnable(counter , "Thread one count is: "));
Thread threadTwo = new Thread(getRunnable(counter , "Thread two count is: "));
threadOne.start();
threadTwo.start();
}
public class FlightCategories {
static final int FIRST_CLASS = 1;
static final int BUSINESS_CLASS = 2;
static final int ECONOMY_CLASS = 3;
public static void main(String[] args){
System.out.println("===== Application Running =====");
try {
FlightCharges firstClass = new FlightCharges(FIRST_CLASS);
firstClass.computeFlightCharges();
FlightCharges businessClass = new FlightCharges(BUSINESS_CLASS);
businessClass.computeFlightCharges();
FlightCharges economyClass = new FlightCharges(ECONOMY_CLASS);
economyClass.computeFlightCharges();
System.out.println("First class charges are "+firstClass.getCharges());
System.out.println("Business class charges are "+businessClass.getCharges());
System.out.println("Economy class charges are "+economyClass.getCharges());
} catch (Exception e){
System.out.println("Encountered error "+e.getMessage());
}
}
}
public class FlightCharges {
private int category = 0;
private double charges = 0;
public FlightCharges(int category) {
this.category = category;
}
public double getCharges() {
return charges;
}
private void setCharges(double charges) {
this.charges = charges;
}
public void computeFlightCharges(){
try {
switch (category){
case 1:
setCharges(500.0);
break;
case 2:
setCharges(250.0);
break;
case 3:
setCharges(150.0);
break;
}
} catch (Exception e){
System.out.println("Exception "+e.getMessage());
}
}
}
public class FlightCategoriesMultithreading {
static final int ECONOMY_CLASS = 3;
static final int BUSINESS_CLASS = 2;
static final int FIRST_CLASS = 1;
public static void main(String[] args){
System.out.println("===== Application Running =====");
try {
FlightChargesMultithreading firstClass = new FlightChargesMultithreading(FIRST_CLASS);
Thread t1 = new Thread(firstClass);
t1.start();
FlightChargesMultithreading businessClass = new FlightChargesMultithreading(BUSINESS_CLASS);
Thread t2 = new Thread(businessClass);
t2.start();
FlightChargesMultithreading economyClass = new FlightChargesMultithreading(ECONOMY_CLASS);
Thread t3 = new Thread(economyClass);
t3.start();
System.out.println("First class charges are "+firstClass.getCharges());
System.out.println("Business class charges are "+businessClass.getCharges());
System.out.println("Economy class charges are "+economyClass.getCharges());
} catch (Exception e){
System.out.println("Encountered error "+e.getMessage());
}
}
}
public class FlightChargesMultithreading implements Runnable{
private int category = 0;
private double charges = 0;
public FlightChargesMultithreading(int category) {
this.category = category;
}
public double getCharges() {
return charges;
}
private void setCharges(double charges) {
this.charges = charges;
}
@Override
public void run() {
try {
switch (category){
case 1:
setCharges(500.0);
break;
case 2:
setCharges(250.0);
break;
case 3:
setCharges(150.0);
break;
}
} catch (Exception e){
System.out.println("Exception "+e.getMessage());
}
}
}
FlightCategoriesMultithreading.java
example above will certainly finish first in most ideal situations and this will be your expected result. The problem occurs in rare cases when one of the remaining threads (t2 or t3) finishes first. In a more complex multithreaded application running hundreds of threads, this is a potential debugging nightmare and replicating these bugs would be hard!public class RaceConditionExample {
private static Runnable getRunnable(Counter counter , String output) {
return () -> {
for (int i = 0 ; i < 1_000_000 ; i++){
counter.counterThenGet();
}
System.out.println(output + counter.getCount());
} ;
}
public static void main(String[] args) {
computeCounter();
}
private static void computeCounter(){
Counter counter = new Counter() ;
Thread threadOne = new Thread(getRunnable(counter , "Thread one count is: "));
Thread threadTwo = new Thread(getRunnable(counter , "Thread two count is: "));
threadOne.start();
threadTwo.start();
}
}
The following code increments the counter after each iteration:
public class Counter {
private int count = 0 ;
public void counterThenGet() {
this.count++ ;
}
public long getCount() {
return count;
}
}
public void counterThenGet() {
this.count++ ; //CRITICAL SECTION
}
The way to fix the critical section is to make it *atomic*, meaning that only one thread can execute within the critical section at a given time. Making the critical section atomic yields the *sequential access* to counter.
To make it atomic, wrap the section in a synchronized lock:
Only a single thread can execute within the *lock** at a given time, avoiding a situation where two threads read the value of the counter, then increment it. Whatever thread is going to be executed will read the value, increment it, and write it back as a single atomic operation.
## The Importance of Visibility in Multithreading
The importance of *visibility* (the memory that an executing thread can see once it’s written) in multithreaded applications cannot be belabored. When thread 1 writes to the memory before thread 2 reads it, the result is a race condition. It’s therefore imperative for Java developers to design, write, and test multithreaded Java applications with the basic tools of thread synchronization:
- **Using a synchronized keyword**: A synchronized keyword ensures that an unlock happens before every subsequent lock on the same monitor.
- **Using a volatile keyword**: A volatile keyword ensures that a write to a variable happens before subsequent reads of that variable.
- **Static initialization**: Static initialization ensures that the entire program is executed once when the class is first accessed and creates an instance of it. This is done by the class loader so that the JVM guarantees thread safety.
## Debugging Race Conditions
There are a couple of ways to debug multithreaded Java applications, but we’ll focus on the three most common ways here.
### 1. Run Code as Sequential Code
Not every bug is related to race conditions, so it’s possible that debugging in a sequential environment would be easier. Alter your code so that it runs sequentially in order to make sure that the bug does not appear in a sequential run.
For example, `#program omp` parallel for:
#pragma omp parallel
for num_thread(N)### 2. Use Log Statements
From within a thread, use logging statements to output debug information to the debugger so that you can form a post-mortem chain that can be followed. For example:
### 3. Use IntelliJ Debugging Tools
Using the [IntelliJ IDEA debugger](https://lightrun.com/debugging/how-to-debug-remotely-in-intellij/), you can **test multithreaded code and reproduce race condition bugs**.
In our multithreading example, the two threads share a counter. Each thread makes a million iterations while incrementing the counter at each iteration consequentially. This would mean that both threads should give you 2 million iterations in total.
To test this scenario, use breakpoints as provided by IntelliJ IDEA.
Set a breakpoint at the getter that fetches the counter.

You will then configure the breakpoint to only suspend the thread in which it was hit. This suspends both threads at the same line. To do this, right-click on the breakpoint, then click **Thread**.

To initiate debugging, click **Run** near the **RaceConditionExample** class and select **Debug**.

Thread 1 prints a result to the window console that is not 2 million. This is an expected result.

Resume the thread by pressing F9 or clicking the **Resume** button in the upper left of the **Debug** window.

From the results, you can see that race conditions are realized. The second thread should be 2 million but instead 1.9 million is printed.
Now run the second code with atomic implementation of the critical section and observe the differences.


From the images above, notice that your race condition bug has been solved and your second iteration totals to 2 million, a result that your code anticipated as the correct output.
## Conclusion
Multithreading has lots of advantages, but can quickly become a debugging nightmare. Mastering the art of debugging is one of the most useful skills that you as a Java developer can use to make your development process smooth.