Whatever method is used, we end up with a Thread class object that has the method start(). This method call starts the thread execution. Let's look at an example. Let's create a class called AThread that extends Thread and overrides its run() method:
public class AThread extends Thread {
int i1, i2;
public AThread(int i1, int i2) {
this.i1 = i1;
this.i2 = i2;
}
public void run() {
for (int i = i1; i <= i2; i++) {
System.out.println("child thread " + (isDaemon() ? "daemon" : "user") + " " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Overriding the run() method is important because otherwise, the thread will do nothing. The Thread class implements the Runnable interface and has the run() method's implementation, but it looks as follows:
public void run() {
if (target != null) {
target.run();
}
}
The variable target holds the value passed in the constructor:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
But our AThread class does not pass any value to the parent class Target; the variable target is null, so the run() method in the Thread class does not do anything.
Now let's use our newly created thread. We expect it to increment variable i from i1 to i2 (these are parameters passed via the constructor) and print its value along with the Boolean value returned by the isDaemon() method, then wait (sleep) for 1 second and increment variable i again.