Java Concurrency
NOTE:
Java has low level object syncronization APIs (wait; notify; interrupt; etc.) and high-level APIs (Executors and synchonised versious of the Collection objects, ex: BlockingQueue).
Its always a good ideia to check if the high-level APIs let you do what you need since they are a great time saver and less error prone.
Example:
Its always a good ideia to check if the high-level APIs let you do what you need since they are a great time saver and less error prone.
Example:
Although you could implement a Producer/Consumer design pattern using the low level API (like: wait; notify; interrupt; etc.) check this links to see how easy and quick it is to do it with the high-level APIs:
- Producer Consumer Design Pattern with Blocking Queue Example in Java
- Producer Consumer using Executors
- check this example: Java synchronized block vs. Collections.synchronizedMap
- Thread-safety with the Java final keyword
- add some examples in this page
The Java platform is designed from the ground up to support concurrent programming, with basic concurrency support in the Java programming language and the Java class libraries. Since version 5.0, the Java platform has also included high-level concurrency APIs (in the java.util.concurrent packages).
There are two basic strategies for using
Thread
objects:- To directly control thread creation and management, simply instantiate
Thread
each time the application needs to initiate an asynchronous task. An application that creates an instance ofThread
must provide the code that will run in that thread. There are two ways to do this:
Provide a Runnable object (this is the preferred way since the Runnable class doesn't need to extend Thread and so can extend any other needed class): The Runnable interface defines a single method, run, meant to contain the code executed in the thread. The Runnable object is passed to the Thread constructor:
public class HelloRunnable implements Runnable { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new Thread(new HelloRunnable())).start(); } }
Subclass Thread: The Thread class itself implements Runnable, though its run method does nothing. An application can subclass Thread, providing its own implementation of run
public class HelloThread extends Thread { public void run() { System.out.println("Hello from a thread!"); } public static void main(String args[]) { (new HelloThread()).start(); } }
- To abstract thread management from the rest of your application, pass the application's tasks to an executor.
Thread priority
Set priority levels for threads that can wait so that it doesn't bogus your system and runs only on idle cpu cycles.
Ex.:
public class PrimeList implements Runnable { private ArrayList<BigInteger> primesFound; private int numPrimes, numDigits; public PrimeList(int numPrimes, int numDigits, boolean runInBackground) { primesFound = new ArrayList<BigInteger>(numPrimes); this.numPrimes = numPrimes; this.numDigits = numDigits; if (runInBackground) { Thread t = new Thread(this); // Use low priority so you don't slow down server. thread.setPriority(Thread.MIN_PRIORITY); t.start(); } else { run(); } }
Thread.sleep causes the current thread to suspend execution for a specified period. This is an efficient means of making processor time available to the other threads of an application or other applications that might be running on a computer system.
Thread Interrupts
An interrupt is an indication to a thread that it should stop what it is doing and do something else. It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate.
A thread sends an interrupt by invoking
interrupt
on the Thread
object for the thread to be interrupted. For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption.myThread.interrupt(): interrupts the Thread;
Thread.interrupted(): checks if the Thread has been interrupted (returns boolean);
Many methods that throw InterruptedException, such as sleep, are designed to cancel their current operation and return immediately when an interrupt is received:
try { Thread.sleep(4000); } catch (InterruptedException e) { // We've been interrupted: no more messages. return; }
Thread Joins
The join method allows one thread to wait for the completion of another.
Calling t.join() inside a thread causes the current thread to pause execution until t's thread terminates.
wait() notify() and notifyAll()
This methods are present in all Objects since they are inherited from java.lang.Object.
Thread wait(): Causes the current thread to wait until another thread invokes the Object.notify() method or the Object.notifyAll() method for this object.
When wait is invoked, the thread releases any intrinsic lock and suspends execution. The invocation of wait does not return until another thread has issued a notification that some special event may have occurred - though not necessarily the event this thread is waiting for (because of this, always invoke
At some future time, another thread will acquire the same lock and invoke Object.notifyAll, informing all threads waiting on that lock that something important has happened.
Guarded Blocks: using wait() and notify() you can apply a technique called guarded block.
wait() notify() and notifyAll()
This methods are present in all Objects since they are inherited from java.lang.Object.
Thread wait(): Causes the current thread to wait until another thread invokes the Object.notify() method or the Object.notifyAll() method for this object.
When wait is invoked, the thread releases any intrinsic lock and suspends execution. The invocation of wait does not return until another thread has issued a notification that some special event may have occurred - though not necessarily the event this thread is waiting for (because of this, always invoke
wait
inside a loop that tests for the condition being waited for. Don't assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true.)At some future time, another thread will acquire the same lock and invoke Object.notifyAll, informing all threads waiting on that lock that something important has happened.
while (empty) { try { wait(); } catch (InterruptedException e) { } }Note: There is a second notification method,
notify
, which wakes up a single thread. Because notify
doesn't allow you to specify the thread that is woken up, it is useful only in massively parallel applications — that is, programs with a large number of threads, all doing similar chores. In such an application, you don't care which thread gets woken up.
Guarded Blocks: using wait() and notify() you can apply a technique called guarded block.
Synchronization
The Java programming language provides two basic synchronization idioms:
- Intrinsic lock: Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.")
Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them. A thread is said to own the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock. - Reentrant Synchronization: Recall that a thread cannot acquire a lock owned by another thread. But a thread can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization. This describes a situation where synchronized code, directly or indirectly, invokes a method that also contains synchronized code, and both sets of code use the same lock. Without reentrant synchronization, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block.
The Java programming language provides two basic synchronization idioms:
- synchronized methods
To make a method synchronized, simply add the synchronized keyword to its declaration:
public class MyCounter { private int c = 0; public synchronized void increment() { c++; } }
Note: When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception. - synchronized statementsUnlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock:
public void addName(String name) { synchronized(this) { lastName = name; nameCount++; } nameList.add(name); }
Volatile variables
a volatile variable is one whose value is always written to and read from "main memory". That means that different threads can access the variable: recall that without using volatile (or some other synchronization mechanism), then thread A doesn't know that thread B might access the variable. So thread A thinks it's fair game to just cache the value of the variable in a register or in its local memory, and we run into problems.
A typical example of a simple variable that is written to and read from different threads is a "stop request" flag allowing one thread to signal to another to finish:
public class StoppableTask extends Thread { private volatile boolean pleaseStop; public void run() { while (!pleaseStop) { // do some stuff... } } public void tellMeToStop() { pleaseStop = true; } }
If the variable were not declared volatile (and without other synchronization), then it would be legal for the thread running the loop to cache the value of the variable at the start of the loop and never read it again. If you don't like infinite loops, this is undesirable.
A danger of "raw" volatile variables is that they can misleadingly make a non-atomic operation look atomic.
For example:
volatile int i;
i += 5;
keep in mind that unary operations (++, --) aren't atomic.
Atomic Access
In programming, an atomic action is one that effectively happens all at once. An atomic action cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side effects of an atomic action are visible until the action is complete.
We have already seen that an increment expression, such as c++, does not describe an atomic action. Even very simple expressions can define complex actions that can decompose into other actions.
However, there are actions you can specify that are atomic:
- Reads and writes are atomic for reference variables and for most primitive variables (all types except long and double);
- Reads and writes are atomic for all variables declared volatile (including long and double variables);
- Deadlock: describes a situation where two or more threads are blocked forever, waiting for each other;
- Starvation: Starvation describes a situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by "greedy" threads. For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.
- Livelock: A thread often acts in response to the action of another thread. If the other thread's action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work. This is comparable to two people attempting to pass each other in a corridor: Alphonse moves to his left to let Gaston pass, while Gaston moves to his right to let Alphonse pass. Seeing that they are still blocking each other, Alphone moves to his right, while Gaston moves to his left. They're still blocking each other, so...
Caution with inconsistent state
public class SynchronizedRGB {
...
public synchronized int getRGB() {
return ((red << 16) | (green << 8) | blue);
}
public synchronized String getName() {
return name;
}
...
}
SynchronizedRGB must be used carefully to avoid being seen in an inconsistent state. Suppose, for example, a thread executes the following code:
SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black"); ... int myColorInt = color.getRGB(); //Statement 1 String myColorName = color.getName(); //Statement 2If another thread invokes color.set after Statement 1 but before Statement 2, the value of myColorInt won't match the value of myColorName. To avoid this outcome, the two statements must be bound together:
synchronized (color) { int myColorInt = color.getRGB(); String myColorName = color.getName(); }
(...) high-level concurrency features introduced with version 5.0 of the Java platform. Most of these features are implemented in the new java.util.concurrent packages. There are also new concurrent data structures in the Java Collections Framework (like: BlockingQueue, ConcurrentMap, ConcurrentHashMap, ConcurrentNavigableMap, ConcurrentSkipListMap).
Executors
Runnable object, and the thread itself, as defined by a Thread object works well for small applications, but in large-scale applications, it makes sense to separate thread management and creation from the rest of the application. Objects that encapsulate these functions are known as executors.
- Executor Interfaces
Thejava.util.concurrent
package defines three executor interfaces:
Executor:
a simple interface that supports launching new tasks.
Ifr
is aRunnable
object, ande
is anExecutor
object you can replace
(new Thread(r)).start();
e.execute(r);
ExecutorService:
a subinterface ofExecutor
, which adds features that help manage the lifecycle, both of the individual tasks and of the executor itself.
Supplements execute with a similar, but more versatile submit method. Like execute, submit accepts Runnable objects, but also accepts Callable objects, which allow the task to return a value.ScheduledExecutorService:
a subinterface ofExecutorService
, supports future and/or periodic execution of task
- Thread Pools are the most common kind of executor implementation.
Most of the executor implementations in java.util.concurrent use thread pools, which consist of worker threads - Fork/Join is a framework (new in JDK 7) for taking advantage of multiple processors.
Sem comentários:
Enviar um comentário