Outline: Concurrency using Java CS 159: Parallel Processing Spring 2007 Processes vs Threads Thread basics Synchronization Locks Examples Avoiding problems Immutable objects Atomic operations High"level concurrency objects Reference: http://java.sun.com/docs/books/tutorial/essential/concurrency/ index.html Processes and Threads A process is basically a self"contained execution environment Speci#cally: a process has its own memory space Threads are $lightweight% processes Threads exist within processes. Each process has at least one. Threads share the process&s resources including memory and open #les Thread Objects Each Thread is associated with an instance of the Thread class. The most straightforward way to create a thread is to instantiate a Thread object each time the application needs to initiate an asynchronous task. You can use an Executor to create or utilize pre"existing threads for you. You hand over the tasks that are required to be done.
First technique: De#ning and Starting a Thread define a Runnable object (the Runnable interface defines a single method, run): 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(); De#ning and Starting a Thread Second technique: subclass the Thread class public class HelloThread extends Thread public void run() System.out.println("Hello from a thread"); public static void main(string args[]) (new HelloThread()).start(); The first technique is more general since your class can subclass anything, not just Thread. The sleep method of the Thread class The Thread class has a static method called sleep that will place the currently executing thread into a sleep state 'execution suspended( for a speci#c period of time. If one thread is interrupted while it is sleeping, an InterruptedException will be thrown by the sleep method of the sleeping thread. Interrupts You can interrupt a thread by invoking the interrupt method on the Thread object. For this to work properly, the interrupted thread must support its own interuption. This can be done: (a) by catching the InterruptedException---if the methods that throw the exception are often being called; (b) by checking if an interrupt has been received---if a long time can go by without calling a method that throws an InterruptedException. This is done by calling Thread.interrupted() which returns true if an interrupt has been received.
Sleep example public class SleepMessages public static void main(string args[]) throws InterruptedException String importantinfo[] = "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" ; for (int i = 0; i < importantinfo.length; i++) //Pause for 4 seconds Thread.sleep(4000); //Print a message System.out.println(importantInfo[i]); Interrupt example for (int i = 0; i < inputs.length; i++) heavycrunch(inputs[i]); //--assume a long compute time if (Thread.interrupted()) //We've been interrupted: no more crunching. return; The need for synchronization Things that can go wrong...thread interference when more than a single thread accesses shared data, there is the possibility for errors...interleaved operations may result in strange results...example of Counter object. class Counter private int c = 0; public void increment() c++; public void decrement() c--; public int value() return c; Memory consistency errors...cache memory consistency issues programmer must ensure the $happens"before% relationship example: int counter = 0; later: counter++; println(counter); if more than one thread accesses counter, then as long as we can ensure that counter++ happens before println(counter) in another thread, then the program produces the correct value.
More on the happens"before relationship when one thread invokes the Thread.start method, every statement that has a happens"before relationship with this statement will have a happens"before relationship with every statement executed in the new thread. Synchronization In Java, there are two synchronization mechanisms: method synchronization statement synchronization. method synchronization is achieved through the synchronized keyword. One thread at a time can enter a synchronized method. See: http://java.sun.com/javase/6/docs/api/java/util/concurrent/packagesummary.html#memoryvisibility When one thread exits a synchronized method, all subsequent threads are guaranteed that a happensbefore relationship exists with statements executed by the #rst thread on the same object. Example public class SynchronizedCounter private int c = 0; public synchronized void increment() c++; public synchronized void decrement() c--; public synchronized int value() return c; So one way to prevent thread interference and ensure memory consistency is to handle all reads and writes of shared variables through synchronized methods. This can be inefficient. Intrinsic Locks and Synchronization Synchronization is achieved through the use of locks known as intrinsic locks or monitor locks. Every object has an intrinsic lock. If one thread wants to access an object&s #elds in an exclusive and consistent manner, it must acquire that object&s intrinsic lock before accessing the #elds and then releasing the lock after it is done. While one thread $owns% an object&s intrinsic lock, no other thread can access it.
Synchronized methods When a synchronized method is invoked by a thread, that thread acquires the intrinsic lock of the method&s object. The lock is released when the thread exits the method 'even through an un"caught exception(. Synchronized statements Synchronized statements are achieved by acquiring the intrinsic lock on a speci#ed object. These are synchronized Example: public void addname(string name) synchronized(this) lastname = name; namecount++; namelist.add(name); This one is not Another Example What about recursion? These objects are just for providing intrinsic locks public class TwoCounters private long c1 = 0; private long c2 = 0; private Object lock1 = new Object(); private Object lock2 = new Object(); A thread cannot acquire a lock that is owned by another thread. public void inc1() synchronized(lock1) c1++; public void inc2() synchronized(lock2) c2++; It can, however, re"acquire a lock that it already owns. This is called reentrant synchronization.
Atomic Access Reads and writes of reference variables and nearly all primitive type variables are atomic. Reads and writes of all variables declared volatile are atomic. Any write to a volatile variable creates a happens"before relationship with subsequent reads of that variable. Liveness The ability of a concurrent application to execute in a timely manner is known as its "liveness". Problems with liveness include "deadlock", "starvation", and "livelock". Deadlock: two threads each waiting for the other (or more complex multi-way links between threads). Starvation: shared resource is held by a greedy thread thereby not allowing other threads that need access to the resource to proceed. Livelock: a thread acts in response to another...who acts in response to the first. The actions prevent either thread from proceeding as each continues to try to respond the other indefinitely. They're not blocked...just doing useless work. Guarded Blocks Here&s a synchronization technique that provides an e)cient way to access a shared resource. The variable $joy% is set to true by some other thread. public synchronized guardedjoy() //--This guard only loops once for each special event, //--which may not be the event we're waiting for. while(joy) try wait(); catch (InterruptedException e) System.out.println( "Joy and efficiency have been achieved"); How it works The thread that has acquired the lock to the method's object finds that joy is false. The wait method releases the lock and blocks until another thread sends out notification that something has happened. The notifyall method reports to all threads waiting on the intrinsic lock owned by the notifying object which cause them to awaken. For example, this would be what the code in the notifying thread might look like: public synchronized notifyjoy() joy = true; notifyall();
An application to a consumer"producer problem The problem: the producer thread generates a product 'in this case a message string( that is consumed by another thread. What&s the di)culty? the consumer cannot attempt to use the product until the producer has generated it. the producer cannot place a new product in the bu*er where the consumer retrieves it until the consumer has removed it The Drop class The Drop class represents the location where the producer leaves the product and the consumer picks it up. One #eld: boolean empty; This #eld is true if there is no product waiting to be picked up by the consumer and false if there is a product Two methods: public synchronized String take() public synchronized void put(string message) See: Drop.java The String is the product that is produced and consumed. The Producer class The Consumer class The Producer class represents the entity that generates the product. One #eld: Drop drop; //--where to leave the product The class implements the Runnable interface One method: public void run() The Consumer class represents the entity that consumes the product. One #eld: Drop drop; //--where to get the product The class implements the Runnable interface One method: public void run() See: Producer.java See: Consumer.java
The Program Immutable Objects public class ProducerConsumerExample public static void main(string[] args) Drop drop = new Drop(); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); Immutable objects are useful in concurrent applications because, since their state cannot change, they cannot become corrupted by multiple threads accessing them concurrently. Here&s an example. the class SynchronizedRGB represents a color. objects of this class have three color components 'red, green, blue( and a name 'e.g. $pitch black%( See: SynchronizedRGB.java Example Example 'continued( It is possible for an object of the SynchronizedRGB class to appear to be in an inconsistent state 'although it will always be in a consistent 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 2 Suppose another thread modi#es the color object by invoking color.set after Statement 1 but before Statement 2. Then the value of mycolorint will be inconsistent with mycolorname. One way to avoid this is to bind the two statements together in a synchronized statement: synchronized (color) int mycolorint = color.getrgb(); String mycolorname = color.getname(); If color was immutable, then this could not happen.
A Strategy for De#ning Immutable Objects 1. Don&t provide $setter% methods + methods that modify #elds or objects referred to by #elds. 2. Make all #elds #nal and private. 3. Don&t allow subclasses to override methods. The simplest way to do this is to declare the class as #nal. A more sophisticated approach is to make the constructor private and construct instances in factory methods. The strategy 'continued( 4. If the instance #elds include references to mutable objects, don&t allow those objects to be changed: Don&t provide methods that modify the mutable objects. Don&t share references to the mutable objects. Never store references to external, mutable objects passed to the constructor; If necessary, create copies, and store references to the copies. Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods. Applying the strategy If we apply this strategy to the SynchronizedRGB class: 1. There are two setter methods in this class. The #rst one, set, arbitrarily transforms the object, and has no place in an immutable version of the class. The second one, invert, can be adapted by having it create a new object instead of modifying the existing one. 2. All #elds are already private; they are further quali#ed as #nal. 3. The class itself is declared #nal. 4. Only one #eld refers to an object, and that object is itself immutable. Therefore, no safeguards against changing the state of $contained% mutable objects are necessary. High"level Concurrency Objects Executors Java 5.0 allows us to,re"use& threads so that it&s not one thread tied to one task. Executors allow us to separate thread management from thread creation. Executor Interfaces The Executor interface has one method: voidexecute(runnable command) This method executes the given command at some time in the future. See: SynchronizedRGB.java
Working with Executors If r is a Runnable object, and e is an Executor object you can replace (new Thread(r)).start(); with e.execute(r); Executor implementations often rely on the presence of thread pools which are worker threads. A simple way to create an executor that uses a #xed thread pool is to invoke the newfixedthreadpool factory method in java.util.concurrent.executors For more information see the reference at Sun Microsystems: http://java.sun.com/docs/books/tutorial/essential/concurrency/ exinter.html