Threads
Consider a single-processor computer. A processor can only work on one sequence of code at a time, yet you can have multiple programs running at once. How can this be?
The answer is that the processor is constantly switching between different programs, sharing resources as efficiently as possible. This happens quickly enough that it appears multiple programs are running simultaneously. So, the operating system is responsible for performing this scheduling, by giving resources to processes and threads. Now, what these terms mean precisely is operating-system specific, but a "process" roughly translates to a program, and can contain multiple threads.
Modern computers tend to offer more than one processor, allowing multiple threads and processes to actually run simultaneously; this is known as "parallelization." If we want our programs to take advantage of these multicore machines, we need to provide multiple threads for the OS to schedule.
As with Sockets, there is a lot of work in the operating system level to make this happen; also, as with Sockets, Java does much of it for us. There are two ways to do this. First, we can extend the Thread class. Alternatively, we can implement the Runnable interface, then pass that Runnable to a Thread constructor.
Example1
Java programs don't start multithreaded; they start as a single thread in the main method, as always, and then spawn new threads. Let's look at a very simple example, that doesn't do anything very interesting. This is an example of the FIRST way of making a thread, in which we extend Thread.
public class SimpleThread extends Thread {
public void run() {
System.out.println("Hello from a thread!");
}
}
public class ThreadMain {
public static void main(String[] args) {
SimpleThread st = new SimpleThread();
st.start();
}
}
Though it doesn't do anything useful, all the parts are there: we have a class that extends Thread, and overwrites the run() method. When we create a Thread object, and call .start(), Thread's start method does the work of setting up the Thread with the OS so it can be scheduled for time on the processor. start() then calls the thread's run() method, which is where we define what work we want the Thread to do.
Example2
This same code can be implemented in the second way, in which we implement Runnable:
public class SimpleRunnable implements Runnable {
public void run() {
System.out.println("Hello from a thread!");
}
}
public class RunnableMain {
public static void main(String[] args) {
Runnable r = new SimpleRunnable();
Thread t = new Thread(r);
t.start();
}
}
It is easier to just extend Thread - it requires one fewer line of code, and one fewer Class/Interface to be aware of. Since we can only extend one class in Java, it is useful to know "implements Runnable" as a backup. Both are valid options.
Example3
Here is an example that uses an "inner class":
public class InnerSimple {
private static class SimpleThread extends Thread {
public void run() {
System.out.println("Hello from a thread!");
}
}
public static void main(String[] args) {
SimpleThread st = new SimpleThread();
st.start();
}
}
Note that this example is exactly like our first SimpleThread example above, but the SimpleThread class is defined INSIDE InnerSimple. Because it is private, it is only available to be created or referred to inside InnerSimple.
Example4
Let's look at a more substantial example:
import java.util.Scanner;
import java.util.ArrayList;
import java.io.File;
import java.io.FileNotFoundException;
public class Max {
private static class MaxThread extends Thread {
private String max = "";
private String[] array;
private int beg, end;
public MaxThread(String[] array, int beg, int end) {
this.array = array;
this.beg = beg;
this.end = end;
}
public void run() {
for (int i = beg; i < end; i++)
if (array[i].length() > max.length())
max = array[i];
}
public String getMax() {
return max;
}
}
public static String max(String[] array) {
//Create and start the first thread
MaxThread mt1 = new MaxThread(array, 0, array.length/2);
mt1.start();
//Create and start the second thread
MaxThread mt2 = new MaxThread(array, array.length/2, array.length);
mt2.start();
//Wait until they both finish
try {
mt1.join();
mt2.join();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
//Find the longer of the two results, and return it
if (mt1.getMax().length() < mt2.getMax().length())
return mt2.getMax();
return mt1.getMax();
}
}
This code offers a method, max, which finds the longest String in an array. It does this by splitting the array in half, and having each thread look for the longest in its part; it then compares the two results from each thread to find the longer of the two.
The fundamentals of this example are the same as in our simple examples above; we define a Thread and its run() method, then get it running with start().
The difference, of course, is that our Thread is now more than just a run method. It has a constructor, and fields, and a getter we can call after the thread is done running. All of that is allowed.
You'll also notice the command join(), which is inherited from Thread, and causes us to wait until the run method of that thread is finished. This is a part of concurrency, which we'll talk about next time.