threads still running after system.exit()

4.3k views Asked by At

I'm learning about multi-threading in Java and was experimenting with threads. I came up with a sort of racing condition code (sort of... excuse this noob) where 2 threads work in a loop (100 times) and races to change a static variable. The goal is when a thread first completes the loop (either running in time-slicing environment or parallelly) the value of the variable is printed and system.exit(int status) is called to terminate program! This is to just find out which thread won the race!

The expected output should be: nameOfTheThreadThatWonTheRace + "--" + valueOf'I'IncrementedInTheLoop

Here is the code:

public class Nest
{
  public static void main(String[] args) throws Exception
  {
    Thread r1 = new Thread(new Racer(),"R1");
    Thread r2 = new Thread(new Racer(),"R2");
    r1.start();
    r2.start();
  }
}

class Racer implements Runnable
{
  public void run()
  {
    for(int i = 0; i <= 100; i++)
    {
      Value.value = Thread.currentThread().getName() + "--" + i;
    }
    Value.printValue();
    System.exit(0);
  }
}

class Value
{
  static String value = null;

  public static void printValue()
  {
    System.out.println(value);
  }
}

However actual output is different (4 runs):

  1. R2--100 R2--100

  2. R2--84 R2--100

  3. R1--100 R1--100

  4. R2--39 R2--100

I'm at loss at why is JVM not halting after any one of the thread reaches 'System.exit(0)' line? Does exit() only shuts down main thread or entire JVM or are the still-executing-threads are stopping the JVM to halt?

Also please explain to me why 2 lines of o/p are being produced?

For extra info:
Processor--> Intel® Core™ i5-7200U CPU @ 2.50GHz × 4 
Ram--> 8GB
OS--> Fed27 workstation

(I'm not yet exposed to java.util.concurrent API 'yet' but I know I can manage threads differently there..)

I appreciate if you can explain in normal threading terms instead of referring to the Concurrent API.

Thanks for help and excuse this noob once again :)

3

There are 3 answers

1
Rahul On BEST ANSWER

I think if you add a little logging to your program, then it can help you to understand what is really going on:

    public void run()
      {
        for(int i = 0; i <= 100; i++)
        {
        if(i==100)
        {
            System.out.println(Thread.currentThread().getName() +" : before setting value 100th time : " + System.currentTimeMillis());
        }
          Value.value = Thread.currentThread().getName() + "--" + i;
          if(i==100)
        {
            System.out.println(Thread.currentThread().getName() +" : after setting value 100th time : " + System.currentTimeMillis());
        }
        }
        System.out.println(Thread.currentThread().getName() +" : before printing : " + System.currentTimeMillis());
        Value.printValue();
        System.out.println(Thread.currentThread().getName() +" : before exiting : " + System.currentTimeMillis());
        System.exit(0);
      }


class Value
{
  static String value = null;

  public static void printValue()
  {
    System.out.println(Thread.currentThread().getName() + " : value : " + value + " : " + System.currentTimeMillis());
  }
}

First thing, If any thread calls a System.exit(), then the JVM terminates(killing all threads!!!). It will not wait for any thread to execute but it calls shutdown hooks, uninvoked finalizers, etc. So, it may take sometime for the JVM to finish. You can read more here:

https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#exit(int)

Now, you have mentioned outputs from only 4 runs. If you run it few more times, I'm sure you will get the below output:

R2--100

This clearly means that the JVM terminated before the other thread could execute.

Now for your outputs, say

R2--100 R2--100

Both thread executed before the JVM could terminate, simple!

One thing to notice is that both thread's name is R2. This is because value variable is static and being shared between the 2 threads. Due to the race condition, before R2 can print the value, it's being set by R1. You can see this if you add the logging that I suggested.

See below sample output:

R2 : before setting value 100th time : 1517579707689
R2 : after setting value 100th time : 1517579707690
R1 : before setting value 100th time : 1517579707689
R1 : after setting value 100th time : 1517579707691
R2 : before printing : 1517579707691
R1 : before printing : 1517579707692
R2 : value : R1--100
R1 : value : R1--100
R2 : before exiting : 1517579707694
R1 : before exiting : 1517579707694
2
Edwin Buck On

It takes time to process System.exit(0) so while the thread that calls it is waiting for the Operating System to kill and reclaim the code that is running on the different cores, the code that is running on the different cores can complete. This makes it look like both threads "won the race".

Remember that in a multi-threaded environment, one thread doesn't have explicit control over the timing of another; not even in system shutdown. If you want that kind of control, you need to write it into the program explicitly (such as by having both threads checking a thread-safe flag, to determine if the race is still being run).

0
werner On

Calling System.exit() will shut down the JVM (please see the javadocs or the language specs).

The static member of Value is being updated by both threads in parallel. The outcome of such an unsafe update is not defined. This is why you see the strage output.

A solution would be to use local variables or fields within the Racer class, as you do not have to share data between your two threads. Furthermore, you even do not need that, as you could get the current thread's name with Thread.currentThread().getName()