You asked in your title:
Is there a way to communicate between two completely independent threads?
(a) Threads within a JVM are never “completely independent”, in that threads share the resources and constraints of the JVM.
(b) Yes, there are many ways for objects to communicate, both within a thread or across threads.
You asked in your comment:
is there an instance of such an object, created at startup by the JVM and to which all running threads could easily grab a reference to ?
Threads don't grab references to objects, the objects executing methods within a thread grab references.
There are many objects available when a JVM launches. For example, System.out
. Any object executing in any thread can access such objects.
Frameworks provide additional objects to be shared across threads. For example, the Jakarta Servlet framework provides an instance of ServletContext
representing the web app for use across all the threads processing requests.
But probably the solution you need is quite simple: Pass the objects needed. When instantiating the Runnable
or Callable
object to be executed within a thread, pass needed resources to the constructor. Instead of thinking in terms of "grabbing" a global object, think of "being handed" needed objects.
In this example, we create two instances of our Runnable
class Counter
, Runnable task1
and Runnable task2
. For each instance, we pass a shared resource, an AtomicInteger
instance. The Atomic…
means the object is internally thread-safe, so we can safely manipulate the single instance across threads.
package work.basil.threading;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class App {
public static void main ( String[] args ) {
App app = new App();
app.demo();
}
private void demo () {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool( 3 );
AtomicInteger count = new AtomicInteger( 0 ); // Shared object.
Runnable task1 = new Counter( count ); // ⟸ Pass shared object to constructor of each `Runnable`/`Callable`.
Runnable task2 = new Counter( count );
scheduledExecutorService.scheduleAtFixedRate( task1 , 0 , 5 , TimeUnit.SECONDS );
scheduledExecutorService.scheduleAtFixedRate( task2 , 0 , 7 , TimeUnit.SECONDS );
try { Thread.sleep( Duration.ofSeconds( 40 ).toMillis() ); } catch ( InterruptedException e ) { e.printStackTrace(); }
scheduledExecutorService.shutdown();
try { scheduledExecutorService.awaitTermination( 5 , TimeUnit.SECONDS ); } catch ( InterruptedException e ) { e.printStackTrace(); }
System.out.println( "count = " + count );
}
class Counter implements Runnable {
AtomicInteger countingTracker;
public Counter ( AtomicInteger countingTracker ) {
this.countingTracker = countingTracker;
}
@Override
public void run () {
Instant instant = Instant.now();
int currentCount = this.countingTracker.incrementAndGet();
System.out.println( "`Counter` object in thread " + Thread.currentThread().getId() + " set this.countingTracker to: " + currentCount + " at " + instant + "." );
}
}
}
The System.out
object does not necessarily report chronologically. As you can see here, the second event appeared on the console before the first. So follow the Instant
moments to see true order of events.
`Counter` object in thread 16 set this.countingTracker to: 2 at 2021-08-06T16:34:02.460073Z.
`Counter` object in thread 15 set this.countingTracker to: 1 at 2021-08-06T16:34:02.459625Z.
`Counter` object in thread 15 set this.countingTracker to: 3 at 2021-08-06T16:34:07.463863Z.
`Counter` object in thread 16 set this.countingTracker to: 4 at 2021-08-06T16:34:09.463869Z.
`Counter` object in thread 17 set this.countingTracker to: 5 at 2021-08-06T16:34:12.459813Z.
`Counter` object in thread 15 set this.countingTracker to: 6 at 2021-08-06T16:34:16.460429Z.
`Counter` object in thread 16 set this.countingTracker to: 7 at 2021-08-06T16:34:17.463699Z.
`Counter` object in thread 16 set this.countingTracker to: 8 at 2021-08-06T16:34:22.463406Z.
`Counter` object in thread 17 set this.countingTracker to: 9 at 2021-08-06T16:34:23.463713Z.
`Counter` object in thread 15 set this.countingTracker to: 10 at 2021-08-06T16:34:27.463391Z.
`Counter` object in thread 16 set this.countingTracker to: 11 at 2021-08-06T16:34:30.463637Z.
`Counter` object in thread 17 set this.countingTracker to: 12 at 2021-08-06T16:34:32.463385Z.
`Counter` object in thread 17 set this.countingTracker to: 13 at 2021-08-06T16:34:37.463273Z.
`Counter` object in thread 15 set this.countingTracker to: 14 at 2021-08-06T16:34:37.463329Z.
`Counter` object in thread 17 set this.countingTracker to: 15 at 2021-08-06T16:34:42.463201Z.
count = 15
You asked:
I am trying to communicate between two threads
One solution is to replace the AtomicInteger
object shared by Runnable
objects across threads with a thread-safe message queue object, as mentioned in Answer by erickson.