Threads are needed for the concurrent execution of a task. In Java, threads are represented by Thread objects: java.lang.Thread extends java.lang.Object implements java.lang.Runnable
is the signature of the Thread class, which will be analyzed now. The package java.lang
is the standard Java package and is implied when declaring an object. Apparently Thread is a subclass of Object - in fact, every single class in Java is a subclass of Object. Finally, Thread implements the interface Runnable. This interface looks like this:
@FunctionalInterface public interface Runnable { public void run(); }
It should become clear what an interface is: a collection of method prototypes that must be defined by implementing classes. So, the Thread class must offer an implementation of public void run()
to comply to the Runnable interface. So far, so good.
Now you have several ways of defining your own thread: as a subclass of Runnable (which contains a lot of boiler-plate code), or by creating Thread objects. Let's first get the subclass method out of the way:
public class EngineWorker implements Runnable { public void run() { //overrides Runnable.run //... } } public class WorkerLauncher { public static void main(String[] args) { //main method new Thread(new EngineWorker()).start(); } }
The EngineWorker
class overrides the run
method to execute some custom actions. Now the WorkerLauncher
main method is the entry point for the JVM (as you should know already); it creates a Thread object, gives it a new EngineWorker object as argument, and then invokes on it the method start()
. This method then calls the run
method of the given Runnable.
Surely, if you want to do a lot of different tasks, it becomes a pain to write a separate class for each one. That is where the anonymous interfaces come to the rescue. Take a look at this beauty:
new Thread(new Runnable() { public void run() { //... } }).start();
Here we offer an anonymous implementation of the Runnable interface, without any detours. It has the same effect as the code above, but it is viable to use this method for throwaway threads. Of course you can also bind the Thread object to a variable to reuse it somewhere else. And now for the final step: any interface with only one public method is a functional interface (as you can see e.g. in the annotation in the Runnable interface). A functional interface can be implemented by using a lambda expression:
new Thread(()->{ //... }).start();
A lambda function is just an anonymous function that you pass a parameter list into (in this case, an empty one) and which produces a result of a certain type (in this case, it must be void
). What happened here is that the functional interface's only public method (public void run
) has been implicitly defined with the term ()->{...}
. Another example of a functional interface is the java.util.function.IntBinaryOperator
which has the single public method int applyAsInt(int a,int b)
(it takes two integers and gives back an integer). Now to utilize this interface, we can write a higher order function (that is a function that takes a function as a parameter):
private static int produce(int p1,int p2,IntBinaryOperator op) { return op.applyAsInt(p1,p2); } public static void main(String[] args) { produce(4,5,(x,y)->{ return x*(y+1)/2; }); //produce returns 12 }
The lambda expression we provide is actually just an implementation of applyAsInt, as can be seen above. Amazing, isn't it?
Back to threads: To suspend the current thread for a given time (in milliseconds), use the static method Thread.sleep(long ms)
. This can be used to output text at a certain pace, or similiar things.
To wait for a Thread t2
to terminate, use t2.join(long ms)
. The current Thread will suspend until t2
dies or the time has run out. Give the argument 0
to wait forever.
To interrupt a misbehaving Thread, call t2.interrupt()
on it. Now, when a Thread gets interrupted, it throws an exception. Exceptions must either be caught by using a try-catch
block or the method that uses the Thread must itself throw the Exception. Are you confused yet? Take a look at this example:
Thread a=new Thread(()->{ try { Thread.sleep(50000); System.out.println("OK, finished!"); } catch(InterruptedException e) { System.err.println("Interrupted!"); return; } }); a.start(); a.join(10000); a.interrupt();
Thread a
contains a try-catch block: it tries the contained actions, and if an Exception happens, it tries to catch the exception by matching with the given Exception type(s). In this case, an InterruptedException
can happen, so we must specify a catch block for exactly this exception. If an InterruptedException gets caught, we output an error message. As you can see, this will definitely happen: After a
is started, the current Thread waits for 10 seconds and then interrupts a
, while the Thread a
is not even close to finishing.
public static void main(String[] args) throws Exception { Thread.sleep(10000); }
main
is declared to throw an Exception. InterruptedException is a subclass of Exception, so this throws
statement will actually throw any possible Exception right out to the screen - unfiltered and in pure Java slang. Maybe this is what you want, maybe you would rather use a try-catch block. The thing to notice here is that the chain of exceptions that are passed from called method to caller can be interrupted and handled (by using a try-catch) or can be passed to the root level (by throwing the exception "higher").
After covering the actions of sleeping, waiting and interrupting, you should have a basic understanding of Threads. Maybe it won't be useful to you right now, but consider that every process inside your operating system can be controlled in such a manner (on Linux, there are the system calls exec
, sleep
, wait
and kill
to manage processes).
Threading can cause a number of problems if more than one instance wants to modify some global data structure:
private static int money=200; //somewhere: new Thread(()->{ money=250; //... }).start(); //somewhere else: new Thread(()->{ money=100; //... }).start();
When it just so happens that the two processes run simultaneously, the first Thread will set the money
to 250 and continue its execution. But when following statements rely on money
being 250, and the second thread overwrites the variable with the value 100 at a bad time, you will get an error - or worse, you won't notice.
private static int money=200; //1: new Thread(()->{ if(money<200) //... else //... }).start(); //2: new Thread(()->{ money=100; //... money=200; //roll back }).start();
Scenario: The first thread reads the value of money
, but the second thread has just modified it to be 100. The first thread then executes the if block, although this might not be the desired behaviour.
private static int[] data={10,10,10,10,10}; //1: new Thread(()->data[0]+=10).start(); //2: new Thread(()->{ int sum=0; for(int elem:data) sum+=elem; //... }).start();
The second thread will compute a wrong sum if the first thread overwrites the first element of data
while the summation is in progress. Therefore the code using the sum will give wrong results.
private static ArrayList<Integer> data=new ArrayList<>(); //1: new Thread(()->data.add(0,42)).start(); //2: new Thread(()->{ int sum=0; for(Integer elem:data) sum+=elem; //... }).start();
Similarly to the Non-repeatable Read, the second thread will get a wrong sum if the first thread sneaks in an element at position 0
of the ArrayList
.
Note that these problems can not be solved with a volatile
declaration of data
(which only ensures that any reading Thread knows the latest value data
). Generally, I would advise you to use immutable objects wherever possible so that two Threads can not mess each other's data up!
Where you have to use a global, mutable data structure, you must rely on synchronization. Java offers the mechanism of intrinsic locks, which is an inherent property of any Java object; a Thread that calls a synchronized
method will acquire the lock of the object that the method was invoked on and hold it until the method returns. Any Thread that wants to access the same object (maybe through a different synchronized method) will suspend until the lock is released by the other Thread.
public class MoneyMan { private int money=200; private synchronized setMoney(int p) { money=p; } //... public static void main(String[] args) { MoneyMan x=new MoneyMan(); new Thread(()->{ x.setMoney(100); //... x.setMoney(200); }).start(); //... } }
The synchronized method ensures that any subsequent Thread knows the up-to-date value of x.money
, so this is a good first step to avoid lower-level problems of concurrency, similar to using volatile
fields. BUT: You can see above that this does not actually solve a Dirty Read vulnerability: in between the two setMoney
calls, the lock of the object x
has been released and is free to acquire for any other Thread that wants to have it. Now if we assume that the assignment of the value 100 to money
should not be noticed by other Threads, because it is rolled back at the end, then our only option is a synchronized statement:
//in main: new Thread(()->{ synchronized(x) { x.setMoney(100); //... x.setMoney(200); } }).start();
The Thread's run
method now contains a synchronized block, which will acquire the intrinsic lock of x
for the entire duration of the block. The synchronized statement specifies the object that it wants to acquire the lock for in the statement head. You can also specify a completely separate lock object:
public class Synchro { private int[] data={10,10,10}; private int money=200; private Object dataLock=new Object(); //can be any type private Object moneyLock=new Object(); public static void main(String[] args) { Synchro me=new Synchro(); new Thread(()->{ synchronized(me.dataLock) { me.data[0]+=10; //... } }).start(); new Thread(()->{ synchronized(me.dataLock) { int sum=0; for(int elem:me.data) sum+=elem; //... } }).start(); new Thread(()->{ synchronized(me.moneyLock) { me.money=100; //... } }).start(); //... } }
Here we separated the Threads that access data
and those who access money
. Of course, we must be very sure that they do no sneaky modifications to unprotected parts of me
, thus sending the whole system down the drain. If we can provide this, there is no need for synchronized methods or volatile
variables.
If you were wondering how the Threads can access a private field, I must congratulate you on having a great attention to detail. The Threads can access these private variables because we start them inside the class Synchro
whose private variables we want to access. Remember that we are giving an anonymous, nested implementation of Runnable
, and nested classes can access private variables of the outer class.
The problems above were concerned with data manipulation, but there are also two very well-known problems pertaining to thread blocking:
public class DeadlockExample { private static int ready1=0,ready2=0; public static void main(String[] args) { new Thread(()->{ while(!ready2) ; ready1=1; //... }).start(); new Thread(()->{ while(!ready1) ; ready2=1; //... }).start(); } }
This is a very simple example of deadlock: each thread waits for the other one to be ready, but this can never happen. The execution is blocked and can never be successfully continued.
Livelock occurs when two Threads are effectively blocking each other, although not being in a dead state. A faulty application could do something like this: Thread 1 passes a message to Thread 2, Thread 2 passes it to Thread 3, and Thread 3 passes it to Thread 1. If the reaction time is small enough, the threads will hardly make any progress because they are passing their message around indefinitely.