Java Threads
What is a Thread?
• Individual and separate unit of execution
that is part of a process
– multiple threads can work together to
accomplish a common goal
• Video Game example
– one thread for graphics
– one thread for user interaction
– one thread for networking
What is a Thread?
Video Game
Process
video networking
interaction
Advantages
• easier to program
– 1 thread per task
• can provide better performance
– thread only runs when needed
– no polling to decide what to do
• multiple threads can share resources
• utilize multiple processors if available
Disadvantage
• multiple threads can lead to deadlock
– much more on this later
• overhead of switching between threads
Making a Thread
• There are methods in the Thread class for managing
threads including creating, starting, and pausing
them. You'll need to know, the following methods:
• start()
• yield()
• sleep()
• run()
• The action happens in the run() method.
• Think of the code you want to execute in a separate
thread as the job to do.
• In other words, you have some work that needs to be
done, say, downloading stock prices in the background
while other things are happening in the program, so
what you really want is that job to be executed in its
own thread.
• So if the work you want done is the job, the one doing
the work (actually executing the job code) is the thread.
• The job always starts from a run() method as
follows:
public void run() {
// your job code goes here
}
Creating Threads (method 1)
• extending the Thread class
– must implement the run() method
– thread ends when run() method finishes
– call .start() to get the thread ready to run
– If you extended the Thread class, instantiation is dead
simple :
– MyThread t = new MyThread()
– or MyThread t = new MyThread(“threadname”)
Creating Threads Example 1
class Output extends Thread {
private String toSay;
public Output(String st) {
toSay = st;
}
public void run() {
try {
for(;;) {
System.out.println(toSay);
sleep(1000);
}
} catch(InterruptedException e) {
System.out.println(e);
}
}
}
Example 1 (continued)
class Program {
public static void main(String [] args) {
Output thr1 = new Output(“Hello”);
Output thr2 = new Output(“There”);
thr1.start();
thr2.start();
}
}
• main thread is just another thread (happens to start first)
• main thread can end before the others do
• any thread can spawn more threads
Creating Threads (method 2)
• implementing Runnable interface
– virtually identical to extending Thread class
– must still define the run()method
– setting up the threads is slightly different
• If you implement Runnable, instantiation is only
slightly less simple. To have code run by a
separate thread, you still need a Thread
instance.
• First, you instantiate your Runnable class:
• MyRunnable r = new MyRunnable();
• Next, you get yourself an instance of
java.lang.Thread (somebody has to run your job…),
and you give it your job!
• Thread t = new Thread(r); // Pass your Runnable to
the Thread
• If you create a thread using the no-arg constructor,
the thread will call its own run() method when it's
time to start working.
• That's exactly what you want when you extend
Thread, but when you use Runnable, you need to
tell the new thread to use your run()method rather
than its own.
• The Runnable you pass to the Thread constructor is
called the target or the target Runnable.
Creating Threads Example 2
class Output implements Runnable {
private String toSay;
public Output(String st) {
toSay = st;
}
public void run() {
try {
for(;;) {
System.out.println(toSay);
Thread.sleep(1000);
}
} catch(InterruptedException e) {
System.out.println(e);
}
}
}
Example 2 (continued)
class Program {
public static void main(String [] args) {
Output out1 = new Output(“Hello”);
Output out2 = new Output(“There”);
Thread thr1 = new Thread(out1);
Thread thr2 = new Thread(out2);
thr1.start();
thr2.start();
}
}
• main is a bit more complex
• everything else identical for the most part
Advantage of Using Runnable
• remember - can only extend one class
• implementing runnable allows class to
extend something else
Thread Constructors
• Besides the no-arg constructor and the constructor
that takes a Runnable (the target, i.e., the instance
with the job to do), there are other overloaded
constructors in class Thread. The constructors we
care about are
■ Thread()
■ Thread(Runnable target)
■ Thread(Runnable target, String name)
■ Thread(String name)
Starting a Thread
Controlling Java Threads
• _.start(): begins a thread running
• wait() and notify(): for synchronization
– more on this later
• _.stop(): kills a specific thread (deprecated)
• _.suspend() and resume(): deprecated
• _.join(): wait for specific thread to finish
• _.setPriority(): 0 to 10 (MIN_PRIORITY to
MAX_PRIORITY); 5 is default (NORM_PRIORITY)
Java Thread Scheduling
• highest priority thread runs
– if more than one, arbitrary
• yield(): current thread gives up processor so
another of equal priority can run
– if none of equal priority, it runs again
• sleep(msec): stop executing for set time
– lower priority thread can run
States of Java Threads
• 4 separate states
– new: just created but not started
– runnable: created, started, and able to run
– blocked: created and started but unable to run
because it is waiting for some event to occur
– dead: thread has finished or been stopped
States of Java Threads
stop(),
start() end of run method
runnable
new dead
wait(), notify(),
I/O request, I/O completion,
suspend() resume()
blocked
Java Thread Example 1
class Job implements Runnable {
private static Thread [] jobs = new Thread[4];
private int threadID;
public Job(int ID) {
threadID = ID;
}
public void run() { do something }
public static void main(String [] args) {
for(int i=0; i<jobs.length; i++) {
jobs[i] = new Thread(new Job(i));
jobs[i].start();
}
try {
for(int i=0; i<jobs.length; i++) {
jobs[i].join();
}
} catch(InterruptedException e) { System.out.println(e); }
}
}
Java Thread Example 2
class Schedule implements Runnable {
private static Thread [] jobs = new Thread[4];
private int threadID;
public Schedule(int ID) {
threadID = ID;
}
public void run() { do something }
public static void main(String [] args) {
int nextThread = 0;
setPriority(Thread.MAX_PRIORITY);
for(int i=0; i<jobs.length; i++) {
jobs[i] = new Thread(new Job(i));
jobs[i].setPriority(Thread.MIN_PRIORITY);
jobs[i].start();
}
try {
for(;;) {
jobs[nextThread].setPriority(Thread.NORM_PRIORITY);
Thread.sleep(1000);
jobs[nextThread].setPriority(Thread.MIN_PRIORITY);
nextThread = (nextThread + 1) % jobs.length;
}
} catch(InterruptedException e) { System.out.println(e); }
}
}
Synchronizing Code
• Can you imagine the havoc that can occur when two
different threads have access to a single instance of
a class, and both threads invoke methods on that
object…and those methods modify the state of the
object?
• In other words, what might happen if two different
threads call, say, a setter method on single object?
• A scenario like that might corrupt an object's state
(by changing its instance variable values in an
inconsistent way), and if that object's state is data
shared by other parts of the program, well, it's too
scary to even visualize.
• Now here's where it starts to get fun.
• Imagine a couple, Fred and Lucy, who both have
access to the account and want to make
withdrawals.
• But they don't want the account to ever be
overdrawn, so just before one of them makes a
withdrawal, he or she will first check the balance
to be certain there's enough to cover the
withdrawal.
• Also, withdrawals are always limited to an amount
of 10, so there must be at least 10 in the account
balance in order to make a withdrawal. Sounds
reasonable. But that's a two-step process:
1. Check the balance.
2. If there's enough in the account (in this example,
at least 10), make the withdrawal.
• What happens if something separates step 1 from
step 2?
• For example, imagine what would happen if Lucy
checks the balance and sees that there's just exactly
enough in the account, 10.
• But before she makes the withdrawal, Fred checks
the balance and also sees that there's enough for his
withdrawal.
• Since Lucy has verified the balance, but not yet
made her withdrawal, Fred is seeing "bad data."
So what can be done?
• The solutionis actually quite simple. We must
guarantee that the two steps of the withdrawal
checking the balance and making the withdrawal—
are never split apart. We need them to always be
performed as one operation, even when the thread
falls asleep in between step 1 and step 2!
• We call this an "atomic operation" (although the
physics is a little outdated, in this case "atomic"
means "indivisible") because the operation,
regardless of the number of actual statements (or
underlying byte code instructions), is completed
before any other thread code that acts on the same
data.
• You can't guarantee that a single thread will stay
running throughout the entire atomic operation.
• But you can guarantee that even if the thread
running the atomic operation moves in and out of
the running state, no other running thread will be
able to act on the same data.
• In other words, If Lucy falls asleep after checking
the balance, we can stop Fred from checking the
balance until after Lucy wakes up and completes her
withdrawal.
Synchronization and Locks
• How does synchronization work? With locks. Every
object in Java has a built-in lock that only comes
into play when the object has synchronized method
code. When we enter a synchronized non-static
method, we automatically acquire the lock
associated with the current instance of the class
whose code we're executing (the this instance).
• Acquiring a lock for an object is also known as
getting the lock, or locking the object, locking on
the object, or synchronizing on the object.
• We may also use the term monitor to refer to the
object whose lock we're acquiring.
• Technically the lock and the monitor are two
different things, but most people talk about the two
interchangeably, and we will too.
• Since there is only one lock per object, if one thread
has picked up the lock, no other thread can pick up
the lock until the first thread releases (or returns) the
lock.
• This means no other thread can enter the
synchronized code (which means it can't enter any
synchronized method of that object) until the lock
has been released.
• Typically, releasing a lock means the thread holding
the lock (in other words, the thread currently in the
synchronized method) exits the synchronized
method.
• At that point, the lock is free until some other thread
enters a synchronized method on that object.
• Remember the following key points about locking
and synchronization:
■ Only methods (or blocks) can be synchronized,
not variables or classes.
■ Each object has just one lock.
■ Not all methods in a class need to be synchronized.
A class can have both synchronized and non-
synchronized methods.
• If two threads are about to execute a synchronized
method in a class, and both threads are using the
same instance of the class to invoke the method,
only one thread at a time will be able to execute the
method.
• The other thread will need to wait until the first one
finishes its method call. In other words, once a
thread acquires the lock on an object, no other
thread can enter any of the synchronized methods in
that class (for that object).
■ If a class has both synchronized and non-
synchronized methods, multiple threads can still
access the class's non-synchronized methods! If you
have methods that don't access the data you're trying
to protect, then you don't need to synchronize them.
• Synchronization can cause a hit in some cases (or
even deadlock if used incorrectly), so you should be
careful not to overuse it.
■ If a thread goes to sleep, it holds any locks it has
—it doesn't release them.
■A thread can acquire more than one lock. For
example, a thread can enter a synchronized method,
thus acquiring a lock, and then immediately invoke
a synchronized method on a different object, thus
acquiring that lock as well. As the stack unwinds,
locks are released again. Also, if a thread acquires a
lock and then attempts to call a synchronized
method on that same object, no problem. The JVM
knows that this thread already has the lock for this
object, so the thread is free to call other
synchronized methods on the same object, using the
lock the thread already has.
■ You can synchronize a block of code rather than a
method.
• Because synchronization does hurt concurrency, you
don't want to synchronize any more code than is
necessary to protect your data. So if the scope of a
method is more than needed, you can reduce the
scope of the synchronized part to something less than
a full method—to just a block. We call this, strangely
a synchronized block, and it looks like this:
class SyncTest {
public void doStuff() {
System.out.println("not synchronized");
synchronized(this) {
System.out.println("synchronized");
}
}
}
• When a thread is executing code from within a
synchronized block, including any method code
invoked from that synchronized block, the code is
said to be executing in a synchronized context.
• The real question is, synchronized on what? Or,
synchronized on which object's lock?
• When you synchronize a method, the object used to
invoke the method is the object whose lock must be
acquired.
• But when you synchronize a block of code, you
specify which object's lock you want to use as the
lock, so you could, for example, use some third-
party object as the lock for this piece of code.
• That gives you the ability to have more than one
lock for code synchronization within a single object.
• Or you can synchronize on the current instance
(this) as in the code above.
• Since that's the same instance that synchronized
methods lock on, it means that you could always
replace a synchronized method with a non-
synchronized method containing a synchronized
block.
So What About Static Methods?
Can They Be Synchronized?
• static methods can be synchronized.
• There is only one copy of the static data you're
trying to protect, so you only need one lock per
class to synchronize static methods—a lock for the
whole class.
• There is such a lock; every class loaded in Java has
a corresponding instance of java.lang.Class
representing that class.
• It's that java.lang.Class instance whose lock is used
to protect the static methods of the class (if they're
synchronized).
• There's nothing special you have to do to
synchronize a static method:
public static synchronized int getCount() {
return count;
}
• The same is also done for synchronized blocks
What Happens If a Thread Can't
Get the Lock?
• If a thread tries to enter a synchronized method and
the lock is already taken, the thread is said to be
blocked on the object's lock.
• Essentially, the thread goes into a kind of pool for
that particular object and has to sit there until the
lock is released and the thread can again become
runnable/running
• Just because a lock is released doesn't mean any
particular thread will get it.
• There might be three threads waiting for a single
lock, for example, and there's no guarantee that the
thread that has waited the longest will get the lock
first.
• When thinking about blocking, it's important to pay
attention to which objects are being used for
locking.
• Threads calling non-static synchronized methods in
the same class will only block each other if they're
invoked using the same instance. That's because
they each lock on this instance, and if they're called
using two different instances, they get two locks,
which do not interfere with each other.
• Threads calling static synchronized methods in the
same class will always block each other—they all
lock on the same Class instance.
• A static synchronized method and a non-static
synchronized method will not block each other,
ever.
• The static method locks on a Class instance while
the non-static method locks on the this instance—
these actions do not interfere with each other at all.
• For synchronized blocks, you have to look at
exactly what object has been used for locking.
(What's inside the parentheses after the word
synchronized?)
• Threads that synchronize on the same object will
block each other.
• Threads that synchronize on different objects will
not.
• Access to static fields should be done from static
synchronized methods.
• Access to non-static fields should be done from
non-static synchronized methods
Thread-Safe Classes
• When a class has been carefully synchronized to
protect its data (using the rules just given, or using
more complicated alternatives), we say the class is
"thread-safe.“
• Many classes in the Java APIs already use
synchronization internally in order to make the class
"thread-safe.“
• For example, StringBuffer and StringBuilder are
nearly identical classes, except that all the methods
in StringBuffer are synchronized when necessary,
while those in StringBuilder are not.
• Generally, this makes StringBuffer safe to use in a
multithreaded environment, while StringBuilder is
not.
• (In return, StringBuilder is a little bit faster because
it doesn't bother synchronizing.)
Thread Deadlock
• Perhaps the scariest thing that can happen to a Java
program is deadlock.
• Deadlock occurs when two threads are blocked,
with each waiting for the other's lock.
• Neither can run until the other gives up its lock, so
they'll sit there forever.
• A deadlock is a state where two, or more, threads are
blocked waiting (Java Doc) for the other blocked waiting
thread (or threads) to finish and thus none of the threads
will ever complete.
• For example, say we have two threads called thread-A and
thread-B.
• These threads are in a deadlock state if thread-A is waiting
for thread-B to finish, while thread-B is waiting for thread-
A to finish as shown in the following image.
• Once in deadlock state, both threads will hang forever.
• This can happen, for example, when thread A hits
synchronized code, acquires a lock B, and then
enters another method (still within the synchronized
code it has the lock on) that's also synchronized.
• But thread A can't get the lock to enter this
synchronized code—block C—because another
thread D has the lock already.
• So thread A goes off to the waiting-for-the-C-lock
pool, hoping that thread D will hurry up and release
the lock (by completing the synchronized method).
• But thread A will wait a very long time indeed,
because while thread D picked up lock C, it then
entered a method synchronized on lock B.
• Obviously, thread D can't get the lock B because
thread A has it.
• And thread A won't release it until thread D releases
lock C. But thread D won't release lock C until after
it can get lock B and continue. And there they sit.
Preventing Deadlocks
• Lock Ordering
• Deadlock occurs when multiple threads need the same locks but
obtain them in different order.
• If you make sure that all locks are always taken in the same order by
any thread, deadlocks cannot occur.
• If a thread, like Thread 3, needs several locks, it must take them in the
decided order. It cannot take a lock later in the sequence until it has
obtained the earlier locks.
Lock Timeout
• Another deadlock prevention mechanism is to put a timeout
on lock attempts meaning a thread trying to obtain a lock
will only try for so long before giving up.
• If a thread does not succeed in taking all necessary locks
within the given timeout, it will backup, free all locks taken,
wait for a random amount of time and then retry.
• The random amount of time waited serves to give other
threads trying to take the same locks a chance to take all
locks, and thus let the application continue running without
locking.