1

I was going through basics of multithreading and was writing a program to understand the difference between using the 2 approaches of creating threads.

I have read that using Runnable allows multiple threads to share same object and wanted to try similar thing while extending Thread. So after creating new object of Demo2 I passed the reference to Thread constructor (similar to what we do in Runnable).

I achieved what I was trying to as objT1, tT1, tT2 incremented value of sum to 3. But while printing out the current thread's name, it prints only demo2.1. I thought that the thread names that will be printed would be demo2.1, t1, t2 since I passed these names in constructor.

    class Main {
    public static void main(String args[]) {
        Demo1 objR1 = new Demo1();
        Demo2 objT1 = new Demo2("demo2.1");

        Thread tT1 = new Thread(objT1,"t1");
        Thread tT2 = new Thread(objT1,"t2");

        Thread tR1 = new Thread(objR1,"tR1");
        Thread tR2 = new Thread(objR1,"tR2");
    
        objT1.start();
        tT1.start();
        tT2.start();

        tR1.start();
        tR2.start();
    }
}


class Demo1 implements Runnable {

    int sum = 0;

    synchronized void calculate() {
        sum = sum +1;   
    }

    public void run()
    {
        calculate();    
        System.out.print(Thread.currentThread().getName()); 
        System.out.println(" "+sum);
    }
}

class Demo2 extends Thread {

    int sum = 0;

    Demo2(String n) {
        super(n);   
    }
    
    synchronized void calculate() {
        sum = sum +1;       
    }

    public void run()
    {
        calculate();        
        System.out.println(this.getName()+" "+sum);
    }
}

Output:

demo2.1 1
demo2.1 2
demo2.1 3
tR1 1
tR2 2

So my question is - Does this snippet create 3 threads? If yes, then why aren't there 3 different names for each thread. If no, then what do these statements do.

Demo2 objT1 = new Demo2("demo2.1");
Thread tT1 = new Thread(objT1,"t1");
Thread tT2 = new Thread(objT1,"t2"); 

I know this must be something trivial but I cannot get answers in tutorials.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
CuriousCurie
  • 113
  • 10
  • My question has been marked as duplicate, but if you read through, my question about thread names is not answered in other posts. Can anyone please help. – CuriousCurie May 01 '22 at 13:37
  • There is a lot of code here, its hard to sort out everything, and in the end either the existing Runnable vs Thread question will cover it or it will be a typo. Please try reading answers on the pre existing question. – Nathan Hughes May 01 '22 at 14:36
  • 1
    @NathanHughes, I re-opened the question and I am answering it. This is not the usual `implements Runnable` vs. `extends Thread` question. OP's confusion runs a bit deeper here. – Solomon Slow May 01 '22 at 14:37

2 Answers2

3

Does this snippet create 3 threads?

Your program creates five threads. Each thread is responsible for one of the five lines of output that you showed.

I thought that the thread names that will be printed would be demo2.1, t1, t2 since I passed these names in constructor.

The five threads are named "demo2.1", "t1", "t2", "tR1", and "tR2", but the "t1" and "t2" threads never get to print their own names. That's because you gave each one of them a Runnable delegate, which happens to be the "demo2.1" thread instance.

The run() method of the "demo2.1" instance (the run() method of the Demo2 class) prints its own name, which is not always the name of the thread that is running it.

In more detail:

Here, you create a new object, which is a Demo2, and which is a Thread, and which implements Runnable, and which is named "demo2.1":

Demo2 objT1 = new Demo2("demo2.1")

When you call objT1.start(), the new thread calls the Demo2.run() method, and that method prints this.getName().

OK, That's simple, but the next bit is not so simple.

Here, you create another new object, which is a Thread that uses the previous objT1 as its Runnable delegate, and which is named "t1":

Thread tT1 = new Thread(objT1,"t1");

When you call tT1.start(), the new thread will call it's delegate's run() method. It will call Demo2.run(). But recall that Demo2 is a Thread with a name, and its run method prints its own name (actually, this.getName()) instead of printing the name of the thread that is running it (which would be Thread.currentThread().getName()).

Recap:

You are using objT1 in two different ways:

  1. You use it as a Thread
  2. You use it again, two more times, as the delegate of a different thread.

All three times, it prints its own name, which is not the same as the name of the thread that is running it in the two cases where you use the object as the delegate of a thread.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • Hi @Solomon Slow, thank you so much for answering. Yes, things are bit more clear now. – CuriousCurie May 01 '22 at 15:21
  • I have a follow up question - are delegate threads (t1,t2) equivalent to normal threads (objT1). – CuriousCurie May 01 '22 at 15:57
  • @CuriousCurie, Equivalent in what way? In both cases, you are creating a new `Thread` instance with a `Runnable` delegate, and the thread runs the delegate's `run()` method. In the case of the threads named "t1" and "t2", the delegate also happens to be an instance of a class that `extends Thread`, but that doesn't really mean anything because none of the `Thread` methods (`start()`, `join()`, `getName()`, ...) is being called at that point. The `Thread` instance that actually controls the thread is different from the `Runnable` `Demo2` instance that also happens to be a `Thread`. – Solomon Slow May 01 '22 at 19:45
  • In my _personal_ opinion, it was a big mistake for the `Thread` class to implement the `Runnable` interface. There's no good reason for it, and it has only ever confused countless newbies ever since. The creators of Java are extremely smart computer scientists, and in my opinion, they had somewhat forgotten what it was like to be a newbie when they invented and/or named certain features of the language. – Solomon Slow May 01 '22 at 19:49
  • Yeah.. what you are explaining makes kind of sense. Thanks again :) – CuriousCurie May 06 '22 at 16:40
0

The Answer by Solomon Slow is informative, and excellent.

In addition, I'd like to add that in modern Java we rarely need to address the Thread class directly. The Executors framework was added in Java 5 to vastly simplify such code as yours.

The key concept is to separate the task(s) from the threads. Focus on the work to be by defining a task as a Runnable (or Callable if returning a result).

In your example, you seem to have two tasks that each result in incrementing a number, and you want to run each task twice. So let's define two classes that implement Runnable. Both increment a counter, but only after pretending to do some amount of work. We simulate that work by sleeping some number of seconds. One sleeps a few seconds, the other sleeps longer, just to imagine two different workloads.

Both classes carry a private member field of an AtomicInteger. That class provides thread-safe ways to increment a number. We need thread-safety protection because we are accessing the same number across threads.

We mark the AtomicInteger member field as final to prevent us from inadvertently re-assigning another object, as we might do during future edits to this code.

public class FastCalc implements Runnable
{
    private final AtomicInteger counter = new AtomicInteger();

    @Override
    public void run ( )
    {
        System.out.println( "INFO - starting `run` on `FastCalc` at " + Instant.now() + " on thread ID " + Thread.currentThread().getId() );  // Beware: Output does *not* necessarily appear on console in chronological order.
        try { Thread.sleep( ThreadLocalRandom.current().nextInt( 2_000 , 4_000 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
        int currentCount = this.counter.incrementAndGet();
        System.out.println( "INFO - result of `run` on `FastCalc` at " + Instant.now() + " is: " + currentCount );
    }

    public int report ( )
    {
        return this.counter.get();
    }
}

And the slower version.

public class SlowCalc implements Runnable
{
    private final AtomicInteger counter = new AtomicInteger();

    @Override
    public void run ( )
    {
        System.out.println( "INFO - starting `run` on `SlowCalc` at " + Instant.now() + " on thread ID " + Thread.currentThread().getId() );  // Beware: Output does *not* necessarily appear on console in chronological order.
        try { Thread.sleep( ThreadLocalRandom.current().nextInt( 8_000 , 12_000 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
        int currentCount = this.counter.incrementAndGet();
        System.out.println( "INFO - result of `run` on `SlowCalc` at " + Instant.now() + " is: " + currentCount );
    }

    public int report ( )
    {
        return this.counter.get();
    }
}

Instantiate each of those tasks.

FastCalc taskFast = new FastCalc();  // Implements `Runnable`.
SlowCalc taskSlow = new SlowCalc();  // Implements `Runnable`.

Instantiate an ExecutorService to handle the threading on our behalf. Usually we get an executor service by way of Executors utility class.

Here we use Executors.newCachedThreadPool() for an executor service that creates any number of threads as needed. This is appropriate in situations where we know we will use a limited number of threads.

ExecutorService executorService = Executors.newCachedThreadPool();

Your example runs each task twice. So we submit each task twice to our executor service.

Remember that both of our classes, FastCalc & SlowCalc, implements Runnable. So we are passing Runnable objects to the submit method here.

executorService.submit( taskFast );  // Passing a `Runnable` object.
executorService.submit( taskSlow );

executorService.submit( taskFast );
executorService.submit( taskSlow );

Then we wait for the tasks to complete. We do this by calling a method that we pulled as boilerplate from the Javadoc of ExecutorService. We changed that code a bit to pass Duration as the amount of time we should reasonably wait for tasks to complete.

this.shutdownAndAwaitTermination( executorService , Duration.ofMinutes( 1 ) );

Here is that boilerplate.

void shutdownAndAwaitTermination ( ExecutorService executorService , Duration duration )
{
    executorService.shutdown(); // Disable new tasks from being submitted
    try
    {
        // Wait a while for existing tasks to terminate
        if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
        {
            executorService.shutdownNow(); // Cancel currently executing tasks
            // Wait a while for tasks to respond to being cancelled
            if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
            { System.err.println( "Pool did not terminate" ); }
        }
    }
    catch ( InterruptedException ex )
    {
        // (Re-)Cancel if current thread also interrupted
        executorService.shutdownNow();
        // Preserve interrupt status
        Thread.currentThread().interrupt();
    }
}

Lastly, we want to report on the results of the run.

System.out.println("Report — taskFast counter: " + taskFast.report() );
System.out.println("Report — taskSlow counter: " + taskFast.report() );

Pulling that code together.

package work.basil.example.threading;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class App2
{
    public static void main ( String[] args )
    {
        App2 app = new App2();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "INFO - Start running demo. " + Instant.now() );

        FastCalc taskFast = new FastCalc();  // Implements `Runnable`.
        SlowCalc taskSlow = new SlowCalc();  // Implements `Runnable`.

        ExecutorService executorService = Executors.newCachedThreadPool();

        executorService.submit( taskFast );  // Passing a `Runnable` object.
        executorService.submit( taskSlow );

        executorService.submit( taskFast );
        executorService.submit( taskSlow );

        this.shutdownAndAwaitTermination( executorService , Duration.ofMinutes( 1 ) );

        System.out.println( "Report — taskFast counter: " + taskFast.report() );
        System.out.println( "Report — taskSlow counter: " + taskFast.report() );

        System.out.println( "INFO - End running demo. " + Instant.now() );
    }


    // Boilerplate pulled from Javadoc of `ExecutorService`.
    // https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/concurrent/ExecutorService.html
    void shutdownAndAwaitTermination ( ExecutorService executorService , Duration duration )
    {
        executorService.shutdown(); // Disable new tasks from being submitted
        try
        {
            // Wait a while for existing tasks to terminate
            if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
            {
                executorService.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if ( ! executorService.awaitTermination( duration.toSeconds() , TimeUnit.SECONDS ) )
                { System.err.println( "Pool did not terminate" ); }
            }
        }
        catch ( InterruptedException ex )
        {
            // (Re-)Cancel if current thread also interrupted
            executorService.shutdownNow();
            // Preserve interrupt status
            Thread.currentThread().interrupt();
        }
    }
}

When run.

INFO - Start running demo. 2022-05-11T20:50:36.796870Z
INFO - starting `run` on `FastCalc` at 2022-05-11T20:50:36.809083Z on thread ID 16
INFO - starting `run` on `SlowCalc` at 2022-05-11T20:50:36.809228Z on thread ID 17
INFO - starting `run` on `SlowCalc` at 2022-05-11T20:50:36.808793Z on thread ID 15
INFO - starting `run` on `FastCalc` at 2022-05-11T20:50:36.808714Z on thread ID 14
INFO - result of `run` on `FastCalc` at 2022-05-11T20:50:40.081938Z is: 1
INFO - result of `run` on `FastCalc` at 2022-05-11T20:50:40.385796Z is: 2
INFO - result of `run` on `SlowCalc` at 2022-05-11T20:50:47.620290Z is: 1
INFO - result of `run` on `SlowCalc` at 2022-05-11T20:50:47.699582Z is: 2
Report — taskFast counter: 2
Report — taskSlow counter: 2
INFO - End running demo. 2022-05-11T20:50:47.703597Z

Regarding your original interest in total number of threads, we can see here by the thread ID numbers that this code uses a total of 4 threads, one thread per task submission.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • HI @Basil Bourque , I've read your example about executorService.... I have a class that implements Runnable where it does some operations and I was thinking to make 15 parallel threads passing current thread count to the class... Is there a way to pass in the class the actual thread count if I use newFixedThreadPool()?... Because I wanted to split 255 IP addresses to work on into 15 threads of 17 IPs addresses each... – Luigino Jan 09 '23 at 08:25
  • @Luigino I cannot understand your question. To what class are you considering passing a thread count? I suspect you are overthinking it. If you have tasks that each take care of one of 255 IP addresses, then write a task class that implements `Runnable`, and has a constructor that takes the IP address as a parameter. Instantiate 255 objects of that class. … – Basil Bourque Jan 09 '23 at 08:55
  • @Luigino … Then determine the number of threads appropriate for your deployment machine. Say you have a 24-core server, so you estimate a limit of 15 threads. Define an executor service as `Executors.newFixedThreadPool( 15 )`. Submit all of your 255 task objects to that service. The service allocates each task to a thread as the threads become available. – Basil Bourque Jan 09 '23 at 08:57
  • @Luigino If that is not clear, post your own Question on Stack Overflow. But get really specific about your needs, your concerns, and your point of confusion. – Basil Bourque Jan 09 '23 at 09:01
  • Hello @Basil Bourque , I was thinking like 1° thread manages ip address range between x.x.x.1 and x.x.x.17 , second thread manages ip address range between x.x.x.18 and x.x.x.34 and so on... so I could have 15 threads that are working on a group of ip to reduce time of waiting because if I manage 255 ip addresses consecutively I could have to wait too much time... – Luigino Jan 09 '23 at 09:14
  • @Luigino If your tasks are independent, then there is no point in your grouping them into gangs of 17. The job of an executor service is to feed task objects to its backing pool of threads; let it do its job. – Basil Bourque Jan 09 '23 at 09:38
  • I was thinking to group into 15 threads of 17 ip addresses range each because let's suppose a try connection timeout of 200ms for each and all them fails it would need all 255 consecutively 200ms x 255 = 51000ms, indeed with 15 parallel thread jobs working at the same time I should wait 200ms x 17 = 3400ms that's less than 51000ms.... – Luigino Jan 09 '23 at 10:02
  • @Luigino You really should post your own Question, with all the details. – Basil Bourque Jan 09 '23 at 18:24