Java Concurrency: Threads and Runnables
Threads are everywhere, they are the basic building block of every server application out there.
Usually in Java using threads is just a combination of Executors and Runnables however let’s have a closer look on a thread and how it works.
Supposing I want to start a thread, it can be as simple as this.
Thread thread = new Thread(() { System.out.println("Running somewhere else"); }); thread.start(); thread.join();
What will happen in this case is that we create a thread and we join the thread until the operation finishes.
If we check the internals of the thread class we can see that in our initialization we passed a Runnable.
So the Runnable essentially instructs the thread what to do. It encapsulates the logic that will be executed once we start a thread.
If we check the source code the Runnable we can see that it is an interface with only one function.
@FunctionalInterface public interface Runnable { public abstract void run(); }
Essentially Runnable is a functional interface.
From the documentation.
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
The runnable can be passed to lambdas, Executors-Thread pools and use it to create standalone threads.
So let’s have a closer look on what a thread does.
If we check the implementation we can see that a Thread implements the Runnable interface.
public class Thread implements Runnable { ... public Thread(Runnable target) { this(null, target, "Thread-" + nextThreadNum(), 0); } ... }
We also displayed a specific thread constructor on purpose, the constructor passes the runnable as a target variable.
If we check the run method of the thread it will execute the Runnable we passed previously.
... @Override public void run() { if (target != null) { target.run(); } }
Supposing no runnable has been passed in one of the constructors the thread will not execute anything.
So let’s see another approach where we shall run a thread by extending it.
public class CustomThread extends Thread { @Override public void run() { System.out.println("No runnable passed"); } }
Which we can run like this
Thread thread = new CustomThread(); thread.start(); thread.join();
So start will essentially execute the run the method that we implemented or the original run method using the target Runnable.
If we check start we shall stumble upon the following code block
... boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } ...
As we see there a method called start0() is invoked.
This method is essentially
private native void start0();
We can see the native methods on the Thread.c class.
static JNINativeMethod methods[] = { {"start0", "()V", (void *)&JVM_StartThread}, ... {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName}, };
This brings us to the actual native code starting the thread which will end up on the actual implementation based on the os used.
For example on linux it would be pthreads.
Published on Java Code Geeks with permission by Emmanouil Gkatziouras, partner at our JCG program. See the original article here: Java Concurrency: Threads and Runnables Opinions expressed by Java Code Geeks contributors are their own. |