CA670 - Concurrent Programming Processeses & Threads
by user
Comments
Transcript
CA670 - Concurrent Programming Processeses & Threads
Java Threads Synchronisation Multicore CA670 - Concurrent Programming Java Threads David Sinclair Java Threads Synchronisation Multicore Processeses & Threads Process A process is a self-contained computational unit with its own execution path and local resources. • Memory • Communication ports An application may be a single process or a set of processes communicating with each other (possibly on different physical machines.) Thread A thread, also known as a lightweight process, is an execution environment. Threads exist with a process and all the threads in a process share the local resources of the process, particularly local memory. While this makes communications between tasks very efficient via shared memory, it gives rise to several issues that need to be handled carefully. Java Threads Synchronisation Multicore Threads in Java Every application starts with one default thread, the main thread. This thread, like any subsequently created thread, has the power to create and kill threads. Each thread created is an instance of the Thread class. There are two approaches to creating a thread. p u b l i c c l a s s H e l l o T h r e a d e x t e n d s Thread { p u b l i c void run ( ) { System . o u t . p r i n t l n ( ” H e l l o World ! ” ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { ( new H e l l o T h r e a d ( ) ) . s t a r t ( ) ; } } Java Threads Synchronisation Multicore Threads in Java (2) While this is the easiest way, it is a bit restrictive in that your class is a subclass of Thread. A more general way is to make your class a subclass of Runnable. Thread is a subclass of Runnable, but Runnable inherits other class that are useful for thread management. p u b l i c c l a s s H e l l o R u n n a b l e implements R u n n a b l e { p u b l i c void run ( ) { System . o u t . p r i n t l n ( ” H e l l o a g a i n ! ” ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { ( new Thread ( new H e l l o R u n n a b l e ( ) ) ) . s t a r t ( ) ; } } We will use this method. Java Threads Synchronisation Multicore Threads in Java (3) Each thread has a priority from 1 to 10. The JVM uses a preemptive, priority-based scheduler that allocates the current time slice as follows. • The thread with the highest priority is selected. • If two or more threads have the same priority, a first-in first-out (FIFO) policy is followed. • If the current thread is blocked or terminates, another thread is scheduled. • If a thread with a higher priority is unblocked or started, it preempts the current thread. A thread can voluntary give up control calling the yield or sleep methods. This voluntary yielding of control is called Cooperative Multitasking. Java Threads Synchronisation Multicore Yields, Sleeps & Interrupts The yield method is a signal to the JVM that the thread is not ding anything critical and is willing to let another thread run on the JVM. The JVM is free to ignore the yield signal. The sleep method suspends the current thread for the specified time period and releases the JVM for another Runnable thread. The interrupt method is a signal to a thread that it should possibly take some new action. How a thread actually responds to an interrupt is solely at the threads discretion. Usually it would stop what it is currently doing. There are 2 ways a thread can know if it has been interrupted. • Catch an InterruptedException exception. • Test the thread’s Interrupted flag by calling the interrupted method. Java Threads Synchronisation Multicore Interrupted Examples This example responds to an InterruptedException by stopping it current activities. try { Thread . s l e e p ( 2 0 0 0 ) ; // s l e e p f o r 2 s e c o n d s } catch ( I n t e r r u p t e d E x c e p t i o n e ) { return ; } This example test the Interrupted flag and throwing an InterruptedException. i f ( Thread . i n t e r r u p t e d ( ) ) { throw new I n t e r r u p t e d E x c e p t i o n ( ) ; } Java Threads Synchronisation Multicore Joins When a thread invokes the join method on another thread, it will wait for that thread to complete. I can be passed a parameter to specify how long it should wait for the other thread to terminate Like the sleep method, the join method also responds to an interrupt signal with an InterruptedException. Java Threads Synchronisation Multicore A Simple Example From Oracle’s Java Tutorials p u b l i c c l a s s SimpleThreads { s t a t i c v o i d threadMessage ( S t r i n g message ) { S t r i n g threadName = Thread . c u r r e n t T h r e a d ( ) . getName ( ) ; System . o u t . f o r m a t ( ”%s : %s%n” , threadName , m e s s a g e ) ; } p r i v a t e s t a t i c c l a s s MessageLoop i m p l e m e n t s R u n n a b l e { p u b l i c void run ( ) { S t r i n g i m p o r t a n t I n f o [ ] = {” Mares e a t o a t s ” , ” Does e a t o a t s ” , ” L i t t l e l a m b s e a t i v y ” , ”A k i d w i l l e a t i v y t o o ” } ; try { for ( int i = 0; i < importantInfo . length ; { Thread . s l e e p ( 4 0 0 0 ) ; threadMessage ( importantInfo [ i ] ) ; } } catch ( I n t e r r u p t e d E x c e p t i o n e ) { t h r e a d M e s s a g e ( ” I wasn ’ t done ! ” ) ; } i ++) } } Java Threads Synchronisation A Simple Example (2) p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) t h r o w s I n t e r r u p t e d E x c e p t i o n { l o n g p a t i e n c e = 1000 ∗ 60 ∗ 6 0 ; i f ( a r g s . l e n g t h > 0) { try { p a t i e n c e = Long . p a r s e L o n g ( a r g s [ 0 ] ) ∗ 1 0 0 0 ; } catch ( NumberFormatException e ) { System . e r r . p r i n t l n ( ” Argument must be an i n t e g e r . ” ) ; System . e x i t ( 1 ) ; } } t h r e a d M e s s a g e ( ” S t a r t i n g MessageLoop t h r e a d ” ) ; l o n g s t a r t T i m e = System . c u r r e n t T i m e M i l l i s ( ) ; Thread t = new Thread ( new MessageLoop ( ) ) ; t . start (); Multicore Java Threads Synchronisation Multicore A Simple Example (3) t h r e a d M e s s a g e ( ” W a i t i n g f o r MessageLoop t h r e a d t o f i n i s h ” ) ; while ( t . i s A l i v e ()) { threadMessage ( ” S t i l l waiting . . . ” ) ; t . join (1000); i f ( ( ( System . c u r r e n t T i m e M i l l i s ( ) − s t a r t T i m e ) > p a t i e n c e ) && t . i s A l i v e ( ) ) { threadMessage ( ” Tired of waiting ! ” ) ; t . interrupt (); t . join (); } } threadMessage ( ” F i n a l l y ! ” ) ; } } Java Threads Synchronisation When Threads Go Bad! Consider the following program. p u b l i c c l a s s TwoThreads { p r i v a t e s t a t i c c l a s s RugbyLoop i m p l e m e n t s R u n n a b l e { p u b l i c void run ( ) { try { f o r ( i n t i = 0 ; i < 1 0 0 ; i ++) { System . o u t . p r i n t ( ” I l i k e ” ) ; Thread . s l e e p ( 1 0 ) ; System . o u t . p r i n t l n ( ” Rugby ” ) ; Thread . s l e e p ( 1 0 ) ; } } catch ( I n t e r r u p t e d E x c e p t i o n e ) { System . o u t . p r i n t l n ( ” Rugby l o o p i n t e r r u p t e d ! ” ) ; } } } Multicore Java Threads Synchronisation Multicore When Threads Go Bad! (2) p r i v a t e s t a t i c c l a s s B a l l e t L o o p implements Runnable { p u b l i c void run ( ) { try { f o r ( i n t i = 0 ; i < 1 0 0 ; i ++) { System . o u t . p r i n t ( ” I h a t e ” ) ; Thread . s l e e p ( 1 0 ) ; System . o u t . p r i n t l n ( ” B a l l e t ” ) ; Thread . s l e e p ( 1 0 ) ; } } catch ( I n t e r r u p t e d E x c e p t i o n e ) { System . o u t . p r i n t l n ( ” B a l l e t l o o p i n t e r r u p t e d ! ” ) ; } } } p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) t h r o w s I n t e r r u p t e d E x c e p t i o n { Thread r = new Thread ( new RugbyLoop ( ) ) ; Thread b = new Thread ( new B a l l e t L o o p ( ) ) ; r . start (); b. start (); r . join (); b. join (); } } Java Threads Synchronisation When Threads Go Bad! (3) When this program is executed the output may not be what was intended. The problem is due to contention on a shared resource, 2 threads trying to update a shared display. While slightly amusing in this case, it can cause major systems failure in other cases. Consider a banking application where 2 threads concurrently try to update your initial account balance of e500 by e100 and e1000 respectively. The relevant code in the 2 threads could be: 1.1 fload balance 2.1 fload balance 1.2 fadd 100 2.2 fadd 1000 1.3 fstore balance 2.3 fstore balance The interleaving {1.1,1.2,1.3,2.1,2.2,2.3} will give the “right” result, but the interleaving {2.1,2.2,1.1,2.3,1.2,1.3} will result in e1000 missing from the balance. Multicore Java Threads Synchronisation Multicore Critical Sections The previous example shows us that some interleaving should never be allowed. In general whenever a thread/process is updating the state of a shared resource, no other thread/process should be allowed to update the same shared resource while the first thread/process is updating it. In this case the shared resource is the shared variable balance. These section of code that cannot be interleaved with related sections of code are called critical sections. If a thread/process is executing a critical section associated with a shared variable balance, then it is OK if other threads are executing: • code of non-critical sections; or • code of critical sections associated with other shared resources. Java Threads Synchronisation Multicore Synchronization Many different techniques (semaphores, monitors, rendezvous, message passing) have been developed to ensure that 2 threads do not execute associated critical sections at the same time. Java uses a variation of the monitor concept called synchronization. This comes in 2 forms: • synchronized methods and; • synchronized statements Java Threads Synchronisation Multicore Synchronized Methods p u b l i c c l a s s BankAccount { private int balance = 0; ... p u b l i c s y n c h r o n i z e d v o i d d e p o s i t ( f l o a t amount ) { b a l a n c e = b a l a n c e + amount ; } ... } A synchronized method has 2 effects: • 2 invocations of synchronized methods on the same object cannot execute at the same time. When a thread is executing a synchronized method of an object, then all other threads that invoke any synchronized methods for the same object are blocked and execution is suspended until the first thread is done with the object. Java Threads Synchronisation Multicore Synchronized Methods (2) • When a synchronized method exits, it creates a happens-before relationship with any subsequent invocation of any synchronized method of the same object. This guarantees that changes to the state of the object are visible to all threads. Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. Every object has an intrinsic lock (also known as a monitor lock) associated with it. A thread that needs exclusive access to an object’s fields needs to acquire the object’s intrinsic lock before accessing them, and then release the intrinsic lock when it is finished. As long as a thread owns the intrinsic lock, any other thread will block when it attempts to acquire the lock. Java Threads Synchronisation Multicore Synchronized Statements The following example synchronises on updating the member variable balance but allow concurrent invocation of the check owner method by many threads. p u b l i c v o i d d e p o s i t ( S t r i n g name , f l o a t b a b l n c e ) { i f ( c h e c k o w n e r ( name ) ) { synchronized ( t h i s ) { b a l a n c e = b a l a n c e + amount ; } } } Java Threads Synchronisation Multicore Synchronized Statements (2) Synchronized statements must specify the object that provides the intrinsic lock, though it can be any object. p u b l i c c l a s s MsLunch { p r i v a t e l o n g c1 = 0 , c2 = 0 ; p r i v a t e O b j e c t l o c k 1 = new O b j e c t ( ) , l o c k 2 = new O b j e c t ( ) ; public void inc1 () { synchronized ( lock1 ) { c1++; } } public void inc2 () { synchronized ( lock2 ) { c2++; } } } Java Threads Synchronisation Multicore Reentrant Synchronization A thread cannot acquire a lock owned by another thread, But it can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization. This allows a thread to directly or indirectly invoke a method that also contains synchronized code, and both the calling code and the called code use the same lock. Otherwise without reentrant synchronization, we would have very complex code to avoid having a thread causing itself to block. Java Threads Synchronisation Multicore Atomic Access An atomic action is one that happens all at once. It cannot be stopped in the middle. It either happens completely, or it doesn’t happen at all. The following are atomic actions. • Reads from and writes to reference variables and for most primitive variables (all types except long and double). • Reads from and writes to all variables declared volatile (including long and double variables). Using simple atomic variable access is more efficient than accessing these variables through synchronized code, but does define the order in which the different threads perform the atomic accesses. Java Threads Synchronisation Multicore Deadlock Deadlock occurs where two or more threads are blocked forever, typically waiting for each other. Consider the following problem called the Dining Philosophers Problem. An institution hires five philosophers to solve a very difficult problem. Each philosopher only engages in two activities thinking and eating. Meals are taken in the dining room which has a table set with five plates and five forks. In the centre of the table is a bowl of spaghetti that is endlessly replenished. The philosophers, not being the most dextrous of individuals, requires two forks to eat; and may only pick up the forks immediately to his left and right. Java Threads Synchronisation Deadlock (2) For this system to operate correctly it is required that: • A philosopher eats only if he has two forks. • No two philosophers can hold the same fork simultaneously. • No deadlock. • No individual starvation. • Efficient behaviour under the absence of contention. This problem is a generalisation of multiple processes accessing a set of shared resources; e.g. a network of computers accessing an array of storage disks. Multicore Java Threads Synchronisation Multicore Deadlock (3) We can model exclusive access to each fork by using synchronized statements. c l a s s P h i l o s o p h e r implements Runnable { public f i n a l int id ; private f i n a l Chopstick [ ] chopsticks ; protected f i n a l ChopstickOrder order ; p u b l i c P h i l o s o p h e r ( i n t id , Chopstick [ ] chopsticks , ChopstickOrder order ) { this . id = id ; this . chopsticks = chopsticks ; this . order = order ; } p u b l i c void run ( ) { while ( true ) { eat ( ) ; } } Java Threads Synchronisation Multicore Deadlock (4) protected void eat () { Chopstick [ ] chopstickOrder = order . getOrder ( getLeft () , getRight ( ) ) ; synchronized ( chopstickOrder [ 0 ] ) { synchronized ( chopstickOrder [ 1 ] ) { Util . sleep (1000); } } } Chopstick getLeft () { return chopsticks [ id ] ; } Chopstick getRight () { r e t u r n c h o p s t i c k s [ ( i d +1) % c h o p s t i c k s . l e n g t h ] ; } } Java Threads Synchronisation Multicore Deadlock (5) This is called a symmetric solution since each task is identical. Symmetric solutions have many advantages, particularly when it comes to load-balancing. However, this system can deadlock under an interleaving in which all five philosophers pick up their left forks together. There are several solutions. • Make one of the philosophers pick up the right fork before the left fork (asymmetric solution). • Make a philosopher release their left fork if they cannot secure their right fork. • Only allow four philosophers into the room at any one time. Java Threads Synchronisation Multicore Lock Objects In order to provide a more sophisticate locking mechanism than synchronized code, Java provides the java.util.concurrent.locks package. In particular we will focus on its most basic interface, Lock. Lock objects work very much like the implicit locks used by synchronized code. As with implicit locks, only one thread can own a Lock object at a time. Lock objects provide a more sophisticated implementation of monitors by providing a wait/notify mechanism, through their associated Condition objects. The main advantage of Lock objects over implicit locks is their ability to back out of an attempt to acquire a lock. The tryLock method backs out if the lock is not available immediately or before a specified timeout has expired. The lockInterruptibly method backs out if another thread sends an interrupt before the lock is acquired. Java Threads Synchronisation Multicore Lock Objects (2) p u b l i c c l a s s GraciousPhilosopher extends Philosopher { p r i v a t e s t a t i c Map c h o p s t i c k L o c k s = new ConcurrentHashMap ( ) ; p u b l i c G r a c i o u s P h i l o s o p h e r ( i n t id , Chopstick [ ] chopsticks , ChopstickOrder order ) { super ( id , chopsticks , order ) ; // E v e r y p h i l o s o p h e r c r e a t e s a l o c k // f o r t h e i r l e f t c h o p s t i c k c h o p s t i c k L o c k s . p u t ( g e t L e f t ( ) , new R e e n t r a n t L o c k ( ) ) ; } protected void eat () { Chopstick [ ] chopstickOrder = order . getOrder ( getLeft () , getRight ( ) ) ; Lock f i r s t L o c k = c h o p s t i c k L o c k s . g e t ( c h o p s t i c k O r d e r [ 0 ] ) ; Lock s e c o n d L o c k = c h o p s t i c k L o c k s . g e t ( c h o p s t i c k O r d e r [ 1 ] ) ; Java Threads Synchronisation Lock Objects (3) firstLock . lock (); try { secondLock . l o c k ( ) ; try { sleep (1000); } finally { secondLock . unlock ( ) ; } } finally { f i r s t L o c k . unlock ( ) ; } } } Multicore Java Threads Synchronisation Multicore Lock Objects (4) Or using the tryLock method p u b l i c c l a s s GraciousPhilosopher extends Philosopher { p r i v a t e s t a t i c Map c h o p s t i c k L o c k s = new ConcurrentHashMap ( ) ; p u b l i c G r a c i o u s P h i l o s o p h e r ( i n t id , Chopstick [ ] chopsticks , ChopstickOrder order ) { super ( id , chopsticks , order ) ; // E v e r y p h i l o s o p h e r c r e a t e s a l o c k // f o r t h e i r l e f t c h o p s t i c k c h o p s t i c k L o c k s . p u t ( g e t L e f t ( ) , new R e e n t r a n t L o c k ( ) ) ; } protected void eat () { Chopstick [ ] chopstickOrder = order . getOrder ( getLeft () , getRight ( ) ) ; Lock f i r s t L o c k = c h o p s t i c k L o c k s . g e t ( c h o p s t i c k O r d e r [ 0 ] ) ; Lock s e c o n d L o c k = c h o p s t i c k L o c k s . g e t ( c h o p s t i c k O r d e r [ 1 ] ) ; Java Threads Synchronisation Multicore Lock Objects (5) Boolean l e f t L o c k = f a l s e , r i g h t L o c k = f a l s e ; try { leftLock = f i r s t L o c k . tryLock ( ) ; r i g h t L o c k = secondLock . tryLock ( ) ; } finally { i f ( l e f t L o c k && r i g h t L o c k ) { sleep (1000); // e a t } i f ( leftLock ) f i r s t L o c k . unlock ( ) ; i f ( r i g h t L o c k ) secondLock . unlock ( ) ; } } } We still have some issues with this solution. Though there is no deadlock, there is the possibility of individual threads starving and fairness to the access of the shared resource. Java Threads Synchronisation Multicore wait() & notify() synchronized methods and blocks acquire and release a lock within a method. To have one methods acquire a lock and another release it, you need to use the wait-notify mechanism. • wait() tells the calling thread to give up the monitor and go to sleep until some other thread enters the same monitor and calls notify( ). • notify() wakes up the first thread that called wait() on the same object. • notifyAll() wakes up all threads blocked on the same object. Java Threads Synchronisation Multicore wait() & notify() (2) p u b l i c c l a s s ThreadA { p u b l i c s t a t i c v o i d main ( S t r i n g [ ] { ThreadB b = new ThreadB ( ) ; b. start (); args ) synchronized (b) { try { System . o u t . p r i n t l n ( ” W a i t i n g f o r b t o c o m p l e t e . . ” ) ; b . wait ( ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { e . printStackTrace ( ) ; } System . o u t . p r i n t l n ( ” T o t a l i s : ” + b . t o t a l ) ; } } } Java Threads Synchronisation Multicore wait() & notify() (3) c l a s s ThreadB e x t e n d s Thread { int total ; @Override p u b l i c void run ( ) { synchronized ( this ) { f o r ( i n t i =0; i <100 ; { t o t a l += i ; } notify (); } } i ++) } Java Threads Synchronisation Multicore Condition Variables Condition variables factor out and Object’s wait, notify and notifyAll methods into distinct objects to give the effect of having multiple wait-sets per object. A Condition instance is intrinsically bound to a lock. To obtain a Condition instance for a particular Lock instance use its newCondition() method. A common paradigm for concurrent programming is the Producer-Consumer paradigm in which Producers generates work for multiple Consumers to process. The Producers are connected to the Consumers via a bounded buffer. Java Threads Synchronisation Multicore Producer-Consumer c l a s s BoundedBuffer { f i n a l Lock l o c k = new R e e n t r a n t L o c k ( ) ; f i n a l Condition n o t F u l l = lock . newCondition ( ) ; f i n a l C o n d i t i o n notEmpty = l o c k . n e w C o n d i t i o n ( ) ; f i n a l O b j e c t [ ] i t e m s = new O b j e c t [ 1 0 0 ] ; i n t putptr , takeptr , count ; p u b l i c void put ( Object x ) throws I n t e r r u p t e d E x c e p t i o n { lock . lock (); try { w h i l e ( c o u n t == i t e m s . l e n g t h ) { notFull . await ( ) ; } items [ putptr ] = x ; i f (++ p u t p t r == i t e m s . l e n g t h ) { putptr = 0; } ++c o u n t ; notEmpty . s i g n a l ( ) ; } finally { lock . unlock ( ) ; } } Java Threads Synchronisation Producer-Consumer (2) p u b l i c Object take ( ) throws I n t e r r u p t e d E x c e p t i o n { lock . lock (); try { w h i l e ( c o u n t == 0 ) { notEmpty . a w a i t ( ) ; } Object x = items [ takeptr ] ; i f (++ t a k e p t r == i t e m s . l e n g t h ) { takeptr = 0; } −−c o u n t ; notFull . signal (); return x ; } finally { lock . unlock ( ) ; } } } Multicore Java Threads Synchronisation Multicore Running Threads on Different Cores We have seen how to create multiple threads and coordinate them via synchronized methods and blocks, as well as via Lock objects. But how do we execute the threads to different cores on a multicore machine? There are 2 mechanisms in Java • Executors Interface and Thread Pools • Fork/Join Framework Java Threads Synchronisation Multicore Executor Interface & Thread Pools The java.util.concurrentpackage provides 3 executor interfaces. • Executor: A simple interface that launches new tasks. • ExecutorService: A subinterface of Executor that adds features that help manage tasks’ lifecycle. • ScheduledExecutorService: A subinterface of ExecutorService that supports future and/or periodic execution of tasks. The Executor interface provides a single method, execute. If r is a Runnable object, and e is an Executor object then e . execute ( r ) ; may simply execute a thread, or it may use an existing worker thread to run r, or using thread pools it may place r in a queue to wait for a worker thread to become available. Java Threads Synchronisation Multicore Executor Example p u b l i c c l a s s WorkerThread i m p l e m e n t s R u n n a b l e { p r i v a t e S t r i n g command ; p u b l i c WorkerThread ( S t r i n g s ) { t h i s . command=s ; } @Override p u b l i c void run ( ) { System . o u t . p r i n t l n ( Thread . c u r r e n t T h r e a d ( ) . getName ()+ ” S t a r t . Command = ”+command ) ; processCommand ( ) ; System . o u t . p r i n t l n ( Thread . c u r r e n t T h r e a d ( ) . getName ()+ ” End . ” ) ; } Java Threads Synchronisation Executor Example (2) p r i v a t e v o i d processCommand ( ) { try { Thread . s l e e p ( 5 0 0 0 ) ; } catch ( I n t e r r u p t e d E x c e p t i o n e ) { e . printStackTrace ( ) ; } } @Override public String toString () { r e t u r n t h i s . command ; } } Multicore Java Threads Synchronisation Multicore Executor Example (3) import j a v a . u t i l . c o n c u r r e n t . E x e c u t o r S e r v i c e ; import j a v a . u t i l . c o n c u r r e n t . Executors ; p u b l i c c l a s s SimpleThreadPool { p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { ExecutorService executor = Executors . newFixedThreadPool ( 1 0 ) ; f o r ( i n t i = 0 ; i < 2 0 ; i ++) { R u n n a b l e w o r k e r = new WorkerThread ( ” ” + i ) ; executor . execute ( worker ) ; } e x e c u t o r . shutdown ( ) ; while (! executor . isTerminated ()) { } System . o u t . p r i n t l n ( ” F i n i s h e d all threads ” ); } } Java Threads Synchronisation Multicore Fork/Join Framework Since Java 7, the Fork/Join framework has been available to distribute threads among multiple cores. This framework adopts a divide-and-conquer approach. If a task can be easily solved the current thread returns its result. Otherwise the thread divides the task into simpler tasks and forks a thread for each sub-task. When all the sub-tasks are completed, the current thread returns its result obtained from combining the results of its sub-tasks. The key difference between the Fork/Join framework and Executor Interface is that the Fork/Join framework implements a work stealing algorithm whereby idle threads steal work from threads that are still busy. Java Threads Synchronisation Multicore Fork/Join Classes A key class is the ForkJoinPool which is an implementation of the ExecutorService that implements the work-stealing algorithm. A ForkJoinPool is instantiated as follows. numberOfCores = Runtime . getRunTIme ( ) . a v a i l a b l e P r o c e s s o r s ( ) ; F o r k J o i n P o o l p o o l = new F o r k J o i n P o o l ( numberOfCores ) ; The size of the pool at any point in time will be adjusted automatically to maintain enough active threads. Unlike the ExecutorService the ForJoinPool does not need to be explicitly shutdown. There are 3 ways to submit tasks to a ForkJoinPool • execute(): asynchronous execution • invoke(): synchronous execution - wait for the result • invoke(): asynchronous execution - returns a Future object that can be used to check the status of the execution and obtain the results. Java Threads Synchronisation Multicore Fork/Join Classes (2) The ForkJoinTask class is an abstract class that is used to create tasks to be executed from the ForkJoinPool. There are 2 subclasses: • RecursiveTask: returns an object of a specified type. • RecursiveAction: has no return object. The status of a task can be checked using: • isDone(): returns true if the tasks has finished execution. • isCompletedNormally(): returns true if the task has completed without cancellation or exception. • isCancelled(): returns true if the task has been cancelled. • isCompletedAbnormally(): returns true if the task has been terminated by an exception. Java Threads Synchronisation Multicore Fork/Join Example i m p o r t j a v a . u t i l . Random ; import j a v a . u t i l . c o n c u r r e n t . ForkJoinPool ; import j a v a . u t i l . c o n c u r r e n t . RecursiveTask ; p u b l i c c l a s s MaximumFinder e x t e n d s R e c u r s i v e T a s k <I n t e g e r > { p r i v a t e s t a t i c f i n a l i n t SEQUENTIAL THRESHOLD = 5 ; private private private final final final i n t [ ] data ; int start ; i n t end ; p u b l i c MaximumFinder ( i n t [ ] d at a , i n t s t a r t , i n t end ) { t h i s . data = data ; this . start = start ; t h i s . end = end ; } p u b l i c MaximumFinder ( i n t [ ] d a t a ) { t h i s ( da ta , 0 , d a t a . l e n g t h ) ; } Java Threads Synchronisation Multicore Fork/Join Example (2) @Override p r o t e c t e d I n t e g e r compute ( ) { f i n a l i n t l e n g t h = end − s t a r t ; i f ( l e n g t h < SEQUENTIAL THRESHOLD) { return computeDirectly ( ) ; } f i n a l int s p l i t = length / 2; f i n a l MaximumFinder l e f t = new MaximumFinder ( dat a , s t a r t , start + split ); l e f t . fork (); f i n a l MaximumFinder r i g h t = new MaximumFinder ( dat a , s t a r t + s p l i t , end ) ; r e t u r n Math . max ( r i g h t . compute ( ) , l e f t . j o i n ( ) ) ; } Java Threads Synchronisation Multicore Fork/Join Example (3) private Integer computeDirectly () { System . o u t . p r i n t l n ( Thread . c u r r e n t T h r e a d ( ) + ” c o m p u t i n g : ” + s t a r t + ” t o ” + end ) ; i n t max = I n t e g e r . MIN VALUE ; f o r ( i n t i = s t a r t ; i < end ; i ++) { i f ( d a t a [ i ] > max ) { max = d a t a [ i ] ; } } r e t u r n max ; } Java Threads Synchronisation Multicore Fork/Join Example (4) p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { // c r e a t e a random d a t a s e t f i n a l i n t [ ] d a t a = new i n t [ 1 0 0 0 0 ] ; f i n a l Random random = new Random ( ) ; f o r ( i n t i = 0 ; i < d a t a . l e n g t h ; i ++) { d a t a [ i ] = random . n e x t I n t ( 1 0 0 ) ; } // s u b m i t t h e t a s k t o t h e p o o l f i n a l F o r k J o i n P o o l p o o l = new F o r k J o i n P o o l ( 4 ) ; f i n a l MaximumFinder f i n d e r = new MaximumFinder ( d a t a ) ; System . o u t . p r i n t l n ( p o o l . i n v o k e ( f i n d e r ) ) ; } }