'Create multiple threads by extending Thread class that share same object in Java
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.
Solution 1:[1]
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:
- You use it as a
Thread
- 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.
Solution 2:[2]
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`.
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |