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 Examplepublic 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(ListBut 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:
Posts (Atom)