Java Threads – version 2
*I want to thank 1lacca for his input (viewable in the comments), I have changed the wording in a few sections, as well as adding the definition of ThreadGroups and the basics of Synchronization.
(version 1 is also available as a text file at the end of the tutorial)
Threads are a way to produce parallel code. Instead of running simply top down code, if a thread is created at (example line 4), the code in the thread begins to run, as well as the code at line 5. These two pieces of code begin to run at the same time. There are many uses for threads, but they must be used with care, that unthreaded code is not dependant on threaded code. A complete description of the different ways to use threads will be described, some reasonable applications with examples and then examples where problems can arise.
Using Threads:The first thing to understand is that when you run a Java application, it is a process. Just like running Notepad, FireFox, etc., they are each a process running under your Operating System and they can run at the same time. Which makes it important to know that a process is a special case of thread, which controls the sub-threads below it. These sub-threads are contained within the same memory, or address space which was allocated to the original process, thus they are able to share code and variables without special commands which are needed to pass information across two processes. The process is also connected to init or the related application, which determines if a process can continue running.
Thus a Java application can create its own threads, which it controls (how it does this is not important for the sake of understanding this tutorial). What is important is that threads running in parallel are not predictable in general, but these parallel segments of code, have the potential to run faster than a completely linear code. If done properly.
In Java a thread is created as a separate class, either within the same class, or a separate class in one of two ways:
Extending class Thread:
CODE
public class Threads extends Thread
{
public void run()
{
//thread code
}
}
Or,
Implementing class Runnable:
CODE
public class Threads implements Runnable
{
public void run()
{
//thread code
}
}
In both cases, the key is the method:
public void run().
When a thread is started, the method which is launched automatically is the run() method. Similar to a constructor of a class, when the class is created.
Using the examples above, to create a Thread extending Thread:
Thread t = new Threads();Creating a Thread with extending Runnable:
Thread t = new Thread(new Threads());Both of these methods are available since the Thread class implements Runnable itself.
Now that the threads are created, when the code within the run() method is wanted, the thread must be started, using the
.start() method:
t.start();This will work in both cases, as both start the Thread object.
NOTE: A ThreadGroup is a class which contains threads, and/or other ThreadGroups. These groups can be given names, and have many of the properties/methods of a Thread. If no ThreadGroup is explicitly used, all threads are added to a default ThreadGroup.
Many other methods are available, the most useful are:
int activeCount()Returns the number of active threads, within the same ThreadGroup. There may be more threads above or below this ThreadGroup, which are not counted.
void checkAccess()This method determines if the current Thread has the right to modify the thread, eg. Variables, calls, etc. This method throws a SecurityException if the Thread is not allowed to be modified.
Thread currentThread()Returns a reference to the currently running Thread. Care should be taken with this method, if multiple threads are running.
void destroy()Similar to using the depreciated and unsafe suspend(), except that this Thread will not respond to resume(). According to Java.Sun.com it has never been properly implemented, and is deadlock prone. There may be circumstances where this is a better option, but should be avoided.
void dumpStack()Prints out a stack trace of the current Thread. Similar to the stack trace of an Exception.
int enumerate(Thread[] tarray)Copies the active threads of the current ThreadGroup to the supplied array.
boolean holdsLock(Object obj)Determines if the current thread holds a lock on the Object obj. This becomes important with many threads, or producer/consumer programs, protecting against deadlock.
void interrupt()Interrupts the current Thread if and only if the Thread is not blocked by another Thread, if the current Thread is blocked, interrupting will cause an Exception to be thrown, and cause the Thread to terminate. To ensure this happens, a global boolean variable thus accessable to all Threads can be used and change it’s value when wanting to stop. This may not stop the Thread if it is loop based, but it has now become blocked, and calling interupt() will stop it immediately. If the Thread was not blocked, this call will set the interrupt status, for use with the following methods.
boolean interrupted() Returns whether the Thread is currently interrupted. Also clears the interrupted state.
boolean isInterrupted()Returns whether the Thread is currently interrupted.
boolean isAlive()Returns if the Thread is alive or not. Eg: started, but not finished yet.
boolean isDaemon()Returns true if the process is in an infinite loop, or is dependent on a Thread which is in an infinite loop, thus making the Thread a Daemon. Also known as a defunct process in Linux.
void join() or
void join(long millis) or
void join(long millis, int nanos)Waits indefinitely, or a supplied length of time, for a Thread to finish. Thus allowing the Threads to “join”.
void setPriority(int newPriority)Takes a value between 1 and 10, 1 = MIN_PRIORITY, 5 = NORM_PRIORITY, 10 = MAX_PRIORITY. 5 is the default, but priority can be important for background processes, etc.
void sleep(long millis) or
void sleep(long millis, int nanos)Forces a Thread to “sleep” or wait the specified length of time.
void yield()Temporarily pauses a Thread, to allow other threads to execute.
Examples:Count Threads:
CODE
public class Threads
{
public int value = 0;
public Threads()
{
for(int i=0;i<10;++i)
{
new SleepThread().start();
}
}
public class SleepThread extends Thread
{
public void run()
{
try
{
sleep(100);
}
catch(Exception e)
{}
}
}
public static void main(String[] args)
{
Threads t = new Threads();
for(int i=0;i<1000;++i)
{
System.out.println(Thread.activeCount());
}
}
}
This example creates 10 threads, on top of the main method thread. Looping long enough, we run through a count of 11, through 1.
Joining:
CODE
public class Threads
{
Thread thread;
public Threads()
{
thread = new Join();
thread.start();
}
private class Join extends Thread
{
public void run()
{
try
{
sleep(10000); //10s sleep
}
catch(Exception e)
{}
}
}
public static void main(String[] args)
{
Threads t = new Threads();
try
{
t.thread.join();
System.out.println("Joined");
}
catch(Exception e)
{}
}
}
The Thread created sleeps 10 seconds, once it finishes, the main method continues, as the Thread has joined the single stream of code.
Yielding:
CODE
public class Threads
{
Thread thread;
public Threads()
{
thread = new Join();
thread.start();
}
private class Join extends Thread
{
public void run()
{
try
{
sleep(10000); //10s sleep
}
catch(Exception e)
{}
}
}
public static void main(String[] args)
{
Threads t = new Threads();
try
{
t.thread.yield();
Thread.sleep(1000); //clear pause
System.out.println("Yield 1");
System.out.println("Yield 2");
System.out.println("Yield 3");
System.out.println("Yield 4");
System.out.println("Yield 5");
System.out.println("Yield 6");
System.out.println("Yield 7"); }
catch(Exception e)
{}
}
}
This allows the main Thread to continue, until finished, once finished the original Thread continues again.
Interrupting, to stop a (blocked) Thread:
CODE
public class Threads2
{
volatile boolean stopped = false;
Thread thread;
public Threads2()
{
thread = new InterruptThread();
thread.start();
}
private class InterruptThread extends Thread
{
public void run()
{
while(!stopped)
{
System.out.println("Thread running");
try
{
Thread.sleep(10000);
}
catch(Exception e)
{
System.out.println("Thread interrupted");
}
}
}
}
public static void main(String[] args)
{
Threads2 t2 = new Threads2();
try{
Thread.sleep(1);
}
catch(Exception e)
{
}
t2.stopped = true;
t2.thread.interrupt();
}
}
t2.thread.interrupt(); this line if removed will cause the loop in the Thread to finish it’s pass (sleeping 10 seconds) before ending. The loop was blocked with the variable change, so simply passing an interrupt message to it, causes an exception and immediate termination.
Most of the other methods are straight forward, and are used on a less frequent basis.
Reasonable Uses:If care is taken with multiple threads, as well as using the yield() method properly to force proper threads to finish in the correct order. A perfect example of proper Thread use, is in GUIs. Most users have used a program where an operation takes a long time, and the GUI stalls: buttons stay depressed, perhaps goes all white, or even gives the classic “(not responding)” message. A simple threading of the button action will correct this entirely.
An example of an unthreaded GUI with a long (infinite) task:
CODE
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Threads
{
JButton jb;
public Threads()
{
JFrame jf = new JFrame();
jf.getContentPane().add(new Display());
jf.setTitle("GUI");
jf.setResizable(true);
jf.setLocation(100,100);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(200,200);
jf.setVisible(true);
jb.addActionListener(new ButtonEvent());
}
private class Display extends JPanel
{
public Display()
{
jb = new JButton("button");
add(jb);
}
}
private class ButtonEvent implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
while(true)
{
}
}
}
public static void main(String[] args)
{
New Threads();
}
}
The button stops, as well as the application in general, in fact it needs to be terminated.
A simple Thread fixes the entire problem. The button can even be pressed again. It is up to the programmer to handle this operation, perhaps disable the button, or have the new operation yield() to the old one. Another option is to create single instances of the threads, thus they are only started, and not created.
CODE
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Threads
{
JButton jb;
public Threads()
{
JFrame jf = new JFrame();
jf.getContentPane().add(new Display());
jf.setTitle("GUI");
jf.setResizable(true);
jf.setLocation(100,100);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(200,200);
jf.setVisible(true);
jb.addActionListener(new ButtonEvent());
}
private class Display extends JPanel
{
public Display()
{
jb = new JButton("button");
add(jb);
}
}
private class ButtonEvent implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
new ButtonThread().start();
}
private class ButtonThread extends Thread
{
public void run()
{
while(true)
{
}
}
}
}
public static void main(String[] args)
{
New Threads();
}
}
*NOTE depending on what this button does, it could become extremely important that these threads are handled by the programmer, and not allowed to run free, as this example allows. Several suggestions of how to handle these threads were supplied above.
Problems with Threads:The main problem with threads is that the threads are running at the same time:
CODE
public class Threads
{
public int value = 0;
public Threads()
{
new aThread1().start();
}
private class aThread1 extends Thread
{
public void run()
{
for(int i=0;i<10;++i)
{
System.out.print("");
}
value = 1;
}
}
public static void main(String args[])
{
Threads t = new Threads();
for(int i=0;i<1000;++i)
{
System.out.print("");
}
System.out.println(t.value);
t.value = 2;
System.out.println(t.value);
}
}
This code when run will regularly give the result:
1
2
If it is run 15 or 20 times however, you will be surprised by a result of:
0
2
It cannot be assumed which thread will run fastest, and since the “unthreaded” results are dependent on the threaded parallel code.
This example shows a realistic situation where both cases have for loops, where one loop runs faster than the other.
This is why full testing is important, and whether it seems necessary or not, all threads should be handled carefully, blocking and yielding when threads are dependent on each other. Threads are most useful when they are unrelated, and allow multiple blocks of code to be completed at the same time.
Synchronization:The entire Thread is not likely to be synchronized, but rather it is a method representing a segment of code which must be synchronized to all Threads.
Synchronization is supported in two types:
Mutual Exclusion: Implemented with Object locks. Allowing multiple threads to run independently on shared data, with the ability to lock each other to prevent conflicts.
Cooperation: Implemented with the wait() and notify() methods of class Object. This method of synchronization is meant to be used when all all threads work towards a single result.
It is important to understand synchronization locks. When a Thread wishes to run code which is synchronized it must have the lock. Having the lock essentially means that no other Thread may run the synchronized code. Once the current Thread finishes with the synchronized code, it releases the lock and allows another Thread to run the synchronized code.
This effectively allows code to run in parallel as long as they do not cause a conflict on another Thread. The code placed in the synchronize method is that which may cause such a conflict.
Implementing the syncronized code is simple. It is a special block of code within a method:
CODE
public void aMethod()
{
synchronized(variable)
{
}
}
Where
variable is an Object which must not be modified by any other synchronized Thread.
Or as a method declaration:
CODE
public synchronized void anotherMethod()
{
}
Where this method will aquire a lock for it’s containing class, as no Object is supplied.
NOTE: A non-synchronized method, may modify variables, while a synchronized method has a lock on this variable’s containing Object. Also take care with inheritence as sychronization is not inherited.
As with Threads themselves synchronization has it’s downfalls. Due to the potential for deadlock. Java has implemented ways to avoid this, by allowing a synchronized method to call another, even if they both require a lock on the same Object.
CODE
public class Threads2
{
public Threads2()
{
thread1 = new Thread1();
thread2 = new Thread1();
thread1.start();
thread2.start();
}
private class Thread1 extends Thread
{
public void run()
{
anotherMethod();
}
}
public synchronized void anotherMethod()
{
{
System.out.println("Entering Synchronized Code");
try{Thread.sleep(10000);}catch(Exception e){}
}
}
public static void main(String[] args)
{
New Threads2();
}
}
Note that there is a 10 second wait between displaying the first line and the second. Yet if the
anotherMethod() is made non-synchronized, they both appear at once, since there is no restriction on the threads in parallel.
When objects are waiting to execute, do to a lock and synchronized code, this is known as either: entering the monitor, or entering the wait queue. Both terms are common as once the Thread is waiting, it has both become a monitor, which is waiting for signals, and waiting for the current locked Thread to finish.
If the current Thread requires an operation by another Thread to complete before continuing, you can reorder the wait queue, by calling wait(long timeout) on the running Thread. This exmaple has thread1 wait until a variable is set by thread2.
CODE
public class Threads2
{
volatile boolean stopped = false;
Thread thread1,thread2;
public Threads2()
{
thread1 = new Thread1();
thread2 = new Thread2();
thread1.start();
thread2.start();
}
private class Thread1 extends Thread
{
public synchronized void run()
{
try
{
while(!stopped)
{
wait(1);
}
}
catch(Exception e)
{
System.out.println("Error");
}
System.out.println("Thread 1 Waited");
}
}
private class Thread2 extends Thread
{
public synchronized void run()
{
System.out.println("Entering: Thread 2");
try{sleep(500);}catch(Exception e){}
stopped = true;
}
}
public static void main(String[] args)
{
New Threads2();
}
}
The other option is to use a constant wait(), which waits until the notify() or notifyAll() method is called to release the wait on a specific Object, it can be any Object a Java supplied or your own.
*On a side note, the Java documentation on wait() and notify() is horrible, and finding examples on java.sun is almost imposisble. This is a simple example which can easily be expanded.
CODE
public class Threads2
{
Thread thread1,thread2;
private Object lock = new Object();
public Threads2()
{
thread1 = new Thread1();
thread2 = new Thread2();
thread1.start();
thread2.start();
}
private class Thread1 extends Thread
{
public void run()
{
synchronized(lock)
{
try
{
lock.wait();
}
catch(Exception e)
{
System.out.println("Error");
}
System.out.println("Thread 1 Waited");
}
}
}
private class Thread2 extends Thread
{
public void run()
{
System.out.println("Entering: Thread 2");
try{sleep(500);}catch(Exception e){}
synchronized(lock)
{
//something which must be done before thread1 finishes
lock.notify();
}
}
}
This can be done a little more dynamically as well:
CODE
public class Threads2
{
volatile boolean stopped = false;
Thread thread1,thread2;
private Object lock = new Object();
public Threads2()
{
thread1 = new Thread1();
thread2 = new Thread2();
thread1.start();
thread2.start();
}
private class Thread1 extends Thread
{
public void run()
{
synchronized(lock)
{
try
{
if(!stopped)
lock.wait();
}
catch(Exception e)
{
System.out.println("Error");
}
System.out.println("Thread 1 Waited");
}
}
}
private class Thread2 extends Thread
{
public void run()
{
System.out.println("Entering: Thread 2");
try{sleep(500);}catch(Exception e){}
synchronized(lock)
{
//something which must be done before thread1 finishes
if(!stopped)
{
stopped = true; //free any other dependencies
lock.notify();
stopped = false; //reset incase this thread is called again
}
}
}
}
public static void main(String args[])
{
New Threads2();
}
}
Hopefully you not only understand how to use threads, but where and why to use them, also the idea behind handling these threads to ensure the proper order of completion on dependent threads.
Threads_Tutorial_version_1.txt ( 11.49k )
Number of downloads: 221