TerminologyExecution Core - The number of cores that your system has. For example, a CPU with four cores can run four operations simultaneously, and thus can run up to four threads simultaneously. If a CPU has only one core and you run four threads, they will do each of their basic CPU operations by turn (which is determined by various parameters). ThreadsA thread is a Java execution environment for running several units of code simultanously. From the Java programmer's point of view, a program starts with just one thread, called the main thread. This thread has the ability to create additional threads. Threads Strategies
Each thread is associated with an instance of the class
Controlling Threads DirectlyDefining and Starting a ThreadThere are two ways to do this:
Notice that both examples invoke Which Method To Use?
Using the Pausing Execution with Sleep
Using
In any case, you cannot assume that invoking The following example uses
//Print messages at four-second intervals
public class SleepMessages {
//main thread
public static void main(String args[]) throws InterruptedException {
int messages[] = {1, 2, 3, 4};
for (int i = 0; i < messages.length; i++) {
//Print a message
System.out.println(messages[i]);
if (i != messages.length-1) {
//Pause for 4 seconds
System.out.println("//Pause for 4 seconds");
Thread.sleep(4000);
}
}
}
}
InterruptsAn interrupt is an indication to a thread to stop what it is doing and do something else. It's up to the programmer to decide how a thread responds to an interrupt.
A thread sends an interrupt by invoking Note: For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption. Supporting InterruptionSupporting interruptions is done in two ways:
For example, here is an example that checks a heavy operation if it was interrupted, and also catches an
public class CheckInterrupts implements Runnable {
public void run() {
// This method takes about 4 seconds
heavyOperation();
while (true) {
// Checks periodically if it was interrupted
if (Thread.interrupted()) {
System.out.println("interrupted");
return;
}
}
}
private void heavyOperation() {
try {
// Sleep for four seconds
Thread.sleep(4000);
} catch (InterruptedException e) {
System.out.println("Sleep interrupted");
return;
}
}
// Main thread
public static void main(String args[]) throws InterruptedException {
Thread t = new Thread(new CheckInterrupts());
t.start();
Thread.sleep(1000);
// Interrupts Thread.sleep(), that is catched in a catch
// block
t.interrupt();
Thread.sleep(1000);
// Interrupts the thread's while loop, that is catched by
// checking the Thread.interrupted() flag
t.interrupt();
}
}
The Interrupt Status Flag
Invoking
By convention, any internal java method that exits by throwing an Joins
The
For example, if
You can also specify a waiting period, However, as with Example
public class JoinTest implements Runnable {
public void run() {
try {
// Sleep for four seconds
System.out.println("Going to sleep");
Thread.sleep(4000);
} catch (InterruptedException e) {
System.out.println("Sleep interrupted");
return;
}
System.out.println("Slept for four seconds");
System.out.println("Exiting thread t");
}
// Main thread
public static void main(String args[]) throws InterruptedException {
Thread t = new Thread(new JoinTest());
t.start();
System.out.println("Waiting for thread t to finish");
t.join();
System.out.println("Thread t completed");
System.out.println("Sleep 1 second");
Thread.sleep(1000);
System.out.println("Woke up again");
}
}
Threads and Concurrency ChallengesThreads are running in concurrency and communicate primarily by sharing access to fields and objects references. This communication is extremely efficient, but makes two kinds of errors possible:
To avoid these errors you need to synchronize access to shared objects using Synchronization. Thread InterferenceInterference happens when two operations are running simultaneously on different threads, while they are acting on the same data, and this causes them to interleave. This means that the two operations consist of multiple steps, and an operation from one thread runs before an operation from current thread finishes, so the sequences of steps overlap. We need to keep in mind that every operation is translated to the basic building blocks of CPU commands, so even a simple operation in Java may be translated to several steps Lets look on the following example:
//Simple Counter
class SimpleCounter {
//Counter instance
private int c = 0;
//increment
public void increment() {
c++;
}
//increment
public void decrement() {
c--;
}
//decrement
public int value() {
return c;
}
}
The single expression
If a
The same goes for the expression
If Thread A invokes
Because "thread interference bugs" are unpredictable, they can be difficult to detect and fix. Memory Consistency Errors (aka Happens-Before Relationship)Memory consistency errors, also known as happens-before relationship, occur when different threads have inconsistent views of what should be the same data. This may happen when a thread holds a value that is outdated because it was already changed by another thread. The key to avoiding memory consistency errors is to understand which operations or steps happens first, and to make sure that later steps will have an updated data. Let's call it the happens-before relationship. This relationship is simply a guarantee that memory writes by one specific statement are visible to another specific statement.
To see this, consider the following example. Suppose a simple
The
Then, shortly afterwards, thread
If the two statements had been executed in the same thread, it would be safe to assume that
the value printed out would be There are many ways to prevent memory consistency errors. You can:
For a list of actions that prevent memory consistency errors, refer to the Summary page of the
Using SynchronizationUsing synchronization may be a good way to be used to prevent both thread interference and memory consistency errors. However, synchronization can introduce other problem that need to be issued - thread contention - which occurs when two or more threads try to access the same resource simultaneously and cause the Java runtime to execute one or more threads more slowly, or even suspend their execution. There are two main forms of thread contention:
The Java programming language provides two basic synchronization idioms:
Synchronized MethodsTo make a method synchronized, simply add the
//Synchronized Counter
public class SynchronizedCounter {
//Counter instance
private int c = 0;
//increment
public synchronized void increment() {
c++;
}
//increment
public synchronized void decrement() {
c--;
}
//decrement
public synchronized int value() {
return c;
}
}
Making these methods
Note: Constructors cannot be synchronized. Synchronizing constructors does not make sense, because only the thread that creates an object should have access to it while it is being constructed.
Warning: When constructing an object that will be shared between threads, you have to confirm that a
reference to the object does not "leak" prematurely. For example, adding an object of class
//Premature Instance
public class PrematureInstance {
//Adding a premature instance to list
public PrematureInstance(List
But then other threads can access the instance PrematureInstance before construction
is complete.
Synchronized methods is an effective way to prevent thread interference and memory consistency errors, but can present problems with liveness, as we'll see later. Intrinsic Locks (aka Monitor Locks, Monitor)Intrinsic Lock, aka Monitor Lock or simply Monitor or Lock is the mechanism used when synchronizing access of threads. Every object has an monitor (intrinsic lock) associated with it. A thread that accesses synchronized code has to acquire the object's monitor before accessing it, and then release the monitor when it is done. A thread is said to own the monitor between the time it has acquired it and released it. As long as a thread owns a monitor, no other thread can acquire the same monitor. The other thread will block when it attempts to acquire the monitor. The monitor release occurs even if the code caused an uncaught exception. When a thread invokes a synchronized method, monitor for that method's object and releases it when the method returns. When a static synchronized method is invoked, the thread acquires the monitor for the class object associated with the class. Thus access to static fields is controlled by a monitor that's distinct from the monitor for any instance of the class. Note: Reentrant Synchronization: A thread cannot acquire a lock owned by another thread, but it can acquire a lock that it already owns. Synchronized StatementsAnother way to create synchronized code is with synchronized statements. When using synchronized statements you must specify the object that provides the monitor (aka intrinsic lock):
//Synchronized Statement
public class SynchronizedStatement {
private long lastTime;
//Synchronized statement on "this" (current instance)
public void synchronizeCurrentInstance() {
synchronized(this) {
this.lastTime = System.currentTimeMillis();
}
System.out.println(lastTime);
}
//Unsynchronized get
public long getLastTime() {
System.out.println("returning lastTime: [" + this.lastTime + "]");
return lastTime;
}
}
The synchronized statement saves the current time. While in the synchronized statement, other threads cannot access
any code on that instance. When the synchronized statement is finished, Synchronizing Instance FieldsThere are some cases when you want to synchronize only specific fields of an instance without locking the entire instance. The following idiom will do the work:
//Synchronized Fields
public class SynchronizedFields {
private long counter1 = 0;
private long counter2 = 0;
//A monitor for counter 1
private Object counter1Lock = new Object();
//A monitor for counter 2
private Object counter2Lock = new Object();
//Increment counter 1
public void incrementCounter1() {
synchronized(counter1Lock) {
counter1++;
}
}
//Decrement counter 1
public void decrementCounter1() {
synchronized(counter1Lock) {
counter1++;
}
}
//Increment counter 2
public void incrementCounter2() {
synchronized(counter2Lock) {
counter2++;
}
}
//Decrement counter 2
public void decrementCounter2() {
synchronized(counter2Lock) {
counter2++;
}
}
}
Use this idiom with extreme care. You must be absolutely sure that access to Atomic AccessAn atomic action is one that happens all at once. It either happens completely, or it doesn't happen at all.
Even simple expressions, such as an increment, However, there are actions you can specify that are atomic:
Atomic actions cannot be interleaved, so they can be used without fear of thread interference. Still, atomic actions does not prevent memory consistency errors. so you still need to synchronize them. Using volatile variables reduces the risk of memory consistency errors, because changes to a volatile variable are always visible to other threads. Using atomic variable is more efficient than synchronized code, but requires more care by the programmer to avoid memory consistency errors.
Some of the classes in the LivenessLiveness is the ability of a concurrent application, or its threads, to execute correctly and respond in a reasonable manner. Common liveness problems are:
DeadlockDeadlock is a situation where two or more threads are blocked forever, waiting for each other. Here's an example. Two adventurers are walking towards a gate, one from north and one from west. Two gatekeepers need to confirm their pass through the gate. The northern person needs to get a passport from the northern gate keeper and than from the southern gate keeper. The southern person needs to get a passport from the southern gate keeper and than from the northern gate keeper. Unfortunately, this two rules conflict with each other when two adventurers might come from north and south at the same time:
//Deadlock example
public class Deadlock {
//Person class
static class Person {
}
//Northern gate keeper
static class GateKeeperNorth {
public static void pass(Person p) {
System.out.println(
"[GateKeeperNorth]"
+ " allows to pass for ["+p.toString()+"]"
);
}
}
//Southern gate keeper
static class GateKeeperSouth {
public static void pass(Person p) {
System.out.println(
"[GateKeeperSouth]"
+ " allows to pass for ["+p.toString()+"]"
);
}
}
//Each PassHandlerThread holds an instance of the person that wants to pass
static abstract class PassHandlerThread implements Runnable{
public Person person;
}
//Approves a person to pass
static class PassHandlerThreadNorth extends PassHandlerThread {
//Sets the person that should pass
public PassHandlerThreadNorth(Person person) {
this.person = person;
}
//Thread entry point
@Override
public void run() {
System.out.println(
"[PassHandlerThreadNorth]"
+ " is waiting to lock [GateKeeperNorth]"
);
synchronized (GateKeeperNorth.class) {
GateKeeperNorth.pass(this.person);
System.out.println(
"[GateKeeperNorth]"
+ " was locked by [PassHandlerThreadNorth]"
);
// Give a chance for the other thread to lock the other gate keeper
try { Thread.sleep(1000); }
catch (InterruptedException e) {}
System.out.println(
"[PassHandlerThreadNorth]"
+ " is waiting to lock [GateKeeperSouth]"
);
synchronized (GateKeeperSouth.class) {
GateKeeperSouth.pass(this.person);
System.out.println(
"[GateKeeperSouth]"
+ " was locked by [PassHandlerThreadNorth]"
);
}
}
}
}
//Approves a person to pass
static class PassHandlerThreadSouth extends PassHandlerThread {
//Sets the person that should pass
public PassHandlerThreadSouth(Person person) {
this.person = person;
}
//Thread entry point
@Override
public void run() {
System.out.println("[PassHandlerThreadSouth] is waiting to lock [GateKeeperSouth]");
synchronized (GateKeeperSouth.class) {
GateKeeperSouth.pass(this.person);
System.out.println("[GateKeeperSouth] was locked by [PassHandlerThreadSouth]");
// Give a chance for the other thread to lock the other gate keeper
try { Thread.sleep(1000); }
catch (InterruptedException e) {}
System.out.println("[PassHandlerThreadSouth] is waiting to lock [GateKeeperNorth]");
synchronized (GateKeeperNorth.class) {
GateKeeperNorth.pass(this.person);
System.out.println("[GateKeeperNorth] was locked by [PassHandlerThreadSouth]");
}
}
}
}
//Main thread
public static void main(String[] arg) {
new Thread(new PassHandlerThreadNorth(new Person())).start();
new Thread(new PassHandlerThreadSouth(new Person())).start();
}
}
Neither of the threads will ever end, because each thread is waiting for the other to release a monitor on a gate keeper. Starvation and LivelockStarvation and livelock are much less common problems in thread synchronization. StarvationStarvation happens when a thread is unable to gain access to a resource and is unable to make progress. This happens by "greedy" threads that holds a monitor (lock) for a resource for a long time. For example, If synchronized method that takes a long time is invoked frequently by different threads, the first thread will have access to the method but the later threads will be blocked until the call of the first thread will release the method's object monitor. For example, suppose that the two adventurers are reaching a gate at the same time, and the gate keeper is not working there anymore and not only that, he took the key with him. So the process of giving a passport to a person takes a long time, as long as forever. This means that there are two threads that are in starvation: the first one is in the process of approval, and the second is waiting to be handled.
//Starvation example
public class Starvation {
//Person class
static class Person {
}
//Gate keeper
static class GateKeeper {
public static synchronized void pass(Person p) {
System.out.println(
"[GateKeeper]"
+ " allows to pass for ["+p.toString()+"]"
);
}
}
//Approves a person to pass
static class PassHandlerThread implements Runnable {
public Person person;
//Sets the person that should pass
public PassHandlerThread(Person person) {
this.person = person;
}
//Thread entry point
@Override
public void run() {
System.out.println(
"[PassHandlerThread]"
+ " is waiting to lock [GateKeeper] for person [" + this.person + "]"
);
synchronized (GateKeeper.class) {
try {
GateKeeper.class.wait();
GateKeeper.pass(this.person);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
//Main thread
public static void main(String[] arg) {
new Thread(new PassHandlerThread(new Person())).start();
new Thread(new PassHandlerThread(new Person())).start();
}
}
LivelockIn livelock, threads are not blocked but instead they are stuck in an endless situation when they react to each other. For example, if an adventurer reaches a gate and two gatekeepers start to argue who will give a pass to the adventurer first, and none of them will let go, the two gatekeepes are stuck in a livelock.
//Livelock example
public class Livelock {
//Person class
static class Person {
}
//Northern gate keeper
static class GateKeeperNorth {
public static void pass(Person p) {
System.out.println("[GateKeeperNorth] allows to pass for ["+p.toString()+"]");
}
//An argue on who handles the person first
public static String whoIsFirst() {
return "Me first";
}
}
//Southern gate keeper
static class GateKeeperSouth {
public static void pass(Person p) {
System.out.println("[GateKeeperSouth] allows to pass for ["+p.toString()+"]");
}
//An argue on who handles the person first
public static String whoIsFirst() {
return "Me first";
}
}
//Approves a person to pass
static class PassHandlerThread implements Runnable {
public Person person;
//Sets the person that should pass
public PassHandlerThread(Person person) {
this.person = person;
}
//Thread entry point
@Override
public void run() {
System.out.println("[PassHandlerThread] is waiting to lock [GateKeeperNorth]");
synchronized(GateKeeperNorth.class) {
synchronized(GateKeeperNorth.class) {
String northResponse = GateKeeperNorth.whoIsFirst();
String southResponse = GateKeeperSouth.whoIsFirst();
while (northResponse.equals("Me first") &&
southResponse.equals("Me first")) {
//wait until one gives up
System.out.println("North response: " + northResponse);
System.out.println("South response: " + southResponse);
}
}
}
}
}
//Main thread
public static void main(String[] arg) {
new Thread(new PassHandlerThread(new Person())).start();
}
}
Guarded BlocksGuarded block is used to check for a condition before the block can proceed. A guarded block, if not written correctly, can consume unnecessary resources. For example, the previous example in the Livelock section not only demonstrates a livelock, but also an incorrect coding of a guarded block. The thread will run and always check for one of the gate keepers to give up.
while (northResponse.equals("Me first") &&
southResponse.equals("Me first")) {
//wait until one gives up
System.out.println("North response: " + northResponse);
System.out.println("South response: " + southResponse);
}
A more efficient guard invokes
while (northResponse.equals("Me first") &&
southResponse.equals("Me first")) {
//wait until one gives up
System.out.println("North response: " + northResponse);
System.out.println("South response: " + southResponse);
try {
GateKeeperNorth.class.wait();
} catch (InterruptedException e) {
System.out.println("[" + GateKeeperNorth.class + "] was interrupted");
}
}
Note: Always invoke
When
After the thread that interrupted or notified the waiting thread will release the lock, the waiting thread
will reacquire the monitor and resumes by returning from the Immutable ObjectsAn object is considered immutable if its state cannot be changed after it is constructed. Using immutable objects is very useful in concurrent applications. Since they cannot change state, they cannot be corrupted by thread interference or memory consistency errors. As for efficiency, many programmers worry about the cost of creating new objects. But in fact, object creation in many times is more efficient then the overhead of the code needed to protect mutable objects from corruption, in multi-threaded application. When writing an immutable object you need to keep to the following rules:
For example, take a look at the following
//Immutable Person
public final class ImmutablePerson {
//final fields
private final String name;
private final int age;
//The final fields are initialized on the constructor
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
}
//name is a reference to an immutable object
//(java.lang.String), and the reference is final,
//so we can return it
public String getName() {
return name;
}
//age is a primitive value and not a reference;
//so we can return it
public int getAge() {
return age;
}
}
High Level Concurrency ObjectsAs of Java 5.0+, several higher-level building blocks were added to the Java platform. These building blocks are needed for more advanced tasks, such as massively concurrent applications.
Most of these new building blocks are implemented in the Here is a list of some of the new features:
Lock Objects
Sophisticated locking idioms are provided in the
The biggest advantage of
For example, examine the following idiom that solves the deadlock problem we saw earlier.
The approve passport thread Alphonse and
Gaston have trained themselves to notice when a friend is about to bow. We model this improvement by requiring that
our
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//LockObjects example
public class LockObjects {
// Person class
static class Person {
private String name;
public Person(String name) {
this.name = name;
}
private String getName() {
return this.name;
}
}
// Northern guard
static class GuardNorth {
private final static Lock lock = new ReentrantLock();
public static void approvePassport(Person p) {
System.out.println("[GuardNorth]" + " approves passport of ["
+ p.getName() + "]");
}
}
// Southern guard
static class GuardSouth {
private final static Lock lock = new ReentrantLock();
public static void approvePassport(Person p) {
System.out.println("[GuardSouth]" + " approves passport of ["
+ p.getName() + "]");
}
}
// Approves a person to pass
static class PassportApprovalThread implements Runnable {
public Person person;
// Sets the person that should pass
public PassportApprovalThread(Person person) {
this.person = person;
}
// Thread entry point
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName() + "]"
+ " is waiting to lock [GuardNorth]");
boolean lockedNorth = false;
while ( ! lockedNorth) {
lockedNorth = GuardNorth.lock.tryLock();
if ( ! lockedNorth) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("Thread [" + Thread.currentThread().getName() + "] locked [GuardNorth]");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Thread [" + Thread.currentThread().getName() + "]"
+ " is waiting to lock [GuardSouth]");
boolean lockedSouth = false;
while ( ! lockedSouth) {
lockedSouth = GuardSouth.lock.tryLock();
if ( ! lockedSouth) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println("Thread [" + Thread.currentThread().getName() + "] locked [GuardSouth]");
GuardNorth.approvePassport(this.person);
GuardSouth.approvePassport(this.person);
System.out.println("Thread [" + Thread.currentThread().getName() + "] "
+ "allowed passport of [" + this.person.getName() + "]");
GuardNorth.lock.unlock();
GuardSouth.lock.unlock();
}
}
// Main thread
public static void main(String[] arg) {
new Thread(
new PassportApprovalThread(new Person("John")),
"t1"
).start();
new Thread(
new PassportApprovalThread(new Person("Jane")),
"t2"
).start();
}
}
ExecutorsIn large-scale applications, it is better to separate the management of thread's lifecycle and the rest of the application. A new Java API, called executors, is out to achieve this seperation. Three concepts are introduced in this API:
And three types of algorithms:
Executor Interfaces
The
Typically, referencing variables of executors is done by their interface and not by their concrete implementation. The
|
Java Threads Tutorial
Java Threads Tutorial
Monday, 6 October 2014
Java Threads Tutorial
Subscribe to:
Comments (Atom)