CS 51590 Exam The exam is on Wednesday, May 7. These problems review the material we covered about processes, pthreads, Java threads, race conditions, mutexes, locks, condition variables, bounded buffers, readers-writer locks, tasks, task graphs, thread pools, futures. Here is all the code we have looked at this semester. http://cs.pnw.edu/~rlkraft/cs51590/for-class/creating-processes.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/pthreads.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/java-threads.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/java-threads-race-conditions.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/producer-consumer.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/bounded-buffer.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/readers-and-writers.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/simultaneous-IO.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/task-graphs.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/tasks-vs-threads.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/task-graphs-in-threadpools.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/computations-in-thread-pools.zip http://cs.pnw.edu/~rlkraft/cs51590/for-class/task-graphs-in-executor-services.zip Problem 1.) Explain in detail how the two threads given below can lead to a race condition. (Hint: Rewrite the ++ operator so that the race condition becomes more obvious.) public class Problem1 { public static int globalCounter; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Task1()), t2 = new Thread(new Task2()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println( globalCounter ); } static class Task1 implements Runnable { public void run() { for (int i = 0; i < 10_000; ++i) { ++globalCounter; } } } static class Task2 implements Runnable { public void run() { for (int i = 0; i < 10_000; ++i) { ++globalCounter; } } } } Problem 2.) The following code outlines a certain kind of synchronization pattern. In what way are the two threads synchronized? (The definition of the Task class is at the end of this document.) import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class Problem2 { private static ReentrantLock lock = new ReentrantLock(); private static Condition cv = lock.newCondition(); private static boolean go = false; private static boolean wait = true; public static void main(String[] args) throws InterruptedException { final Thread t1 = new Thread( ()->{ for (int i = 0; i < 6; ++i) { try { System.out.println(i + " ========================="); new Task(1).run(); lock.lock(); cv.signal(); lock.unlock(); new Task(1).run(); lock.lock(); if (wait) { wait = false; cv.await(); } else { cv.signal(); wait = true; } lock.unlock(); } catch(InterruptedException e){} } }); final Thread t2 = new Thread( ()->{ for (int i = 0; i < 6; ++i) { try { lock.lock(); if (! go) cv.await(); go = false; lock.unlock(); new Task(2).run(); lock.lock(); if (wait) { wait = false; cv.await(); } else { cv.signal(); wait = true; } lock.unlock(); } catch(InterruptedException e){} } }); t1.start(); t2.start(); } } Problem 3.) The following code outlines a certain kind of synchronization pattern. In what way are the two threads synchronized? import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class Problem3 { private static ReentrantLock lock = new ReentrantLock(); private static Condition cv = lock.newCondition(); private static boolean go = false; public static void main(String[] args) throws InterruptedException { final Thread t1 = new Thread( ()->{ for (int i = 0; i < 6; ++i) { try { System.out.println(i + " ========================="); new Task(1).run(); lock.lock(); go = true; cv.signal(); cv.await(); lock.unlock(); new Task(1).run(); } catch(InterruptedException e){} } }); final Thread t2 = new Thread( ()->{ for (int i = 0; i < 6; ++i) { try { lock.lock(); if (! go) cv.await(); lock.unlock(); new Task(2).run(); lock.lock(); cv.signal(); go = false; lock.unlock(); } catch(InterruptedException e){} } }); t1.start(); t2.start(); } } Problem 4.) Write a program, similar to the previous two problems, that would have output similar to the following. 0 ========================= t13,3 t14,4 t14,3 t13,2 t13,1 t14,2 t13,0 t14,1 t14,0 t13,3 t13,2 t13,1 t13,0 1 ========================= t13,5 t14,3 t13,4 t14,2 t13,3 t14,1 t14,0 t13,2 t13,1 t13,0 t13,2 t13,1 t13,0 2 ========================= t13,4 t14,9 t14,8 t13,3 t14,7 t13,2 t14,6 t13,1 t14,5 t13,0 t14,4 t14,3 t14,2 t14,1 t14,0 t13,4 t13,3 t13,2 t13,1 t13,0 Problem 5.) Suppose we need to implement this task graph. task1 task2 task3 \ \ / \ \ / \ task4 \ / \ / task5 Add the correct fork/join schedule, for this task graph, to the main() method below. public class Problem5 { public static void main(String[] args) throws InterruptedException { final Task task1 = new Task(1); final Task task2 = new Task(3); final Task task3 = new Task(5); final Task task4 = new Task(4); final Task task5 = new Task(2); final Thread t1 = new Thread(task1); final Thread t2 = new Thread(task2); final Thread t3 = new Thread(task3); final Thread t4 = new Thread(task4); final Thread t5 = new Thread(task5); // Write a correct fork/join schedule. } } Problem 6.) Suppose that we have seven tasks and seven threads defined as in the previous problem. Draw the task graph that this fork/join schedule implements. t1.start(); t1.join(); t2.start(); t3.start(); t2.join(); t4.start(); t5.start(); t4.join(); t5.join(); t6.start(); t6.join(); t3.join(); t7.start(); problem 7.) Suppose we need to implement this task graph. task1 task2 / \ / \ / \ / \ task3 task4 task5 \ / \ / \ / \ / task6 task7 (a) Suppose that we define seven tasks and seven threads in a manner similar to Problem 5. What is right and what is wrong with the following fork/join schedule? t1.start(); t2.start(); t1.join(); t3.start(); t2.join(); t5.start(); t4.start(); t4.join(); t5.join(); t7.start(); t3.join(); t6.start(); (b) Write a program that implements this task graph using just three threads but with the maximum parallelism. Use whatever synchronization strategy you want. (c) Re-implement this task graph on an ExecutorService thread pool using Futures in a manner that cannot deadlock (even if the thread pool has a single worker thread) but will have maximum parallelism if the thread pool has at least three worker threads. Problem 8.) What is the advantage of using a readers-writer lock? Problem 9.) (a) What are the advantages and disadvantages to a thread pool having multiple task queues? (b) What are the advantages and disadvantages to a thread pool having a single task queue? Problem 10.) // Task class for the task graphs. import java.util.Random; import java.util.concurrent.TimeUnit; public class Task implements Runnable { private final Random random = new Random(); private final int colunmNumber; Task(int columnNumber) { this.colunmNumber = columnNumber; } @Override public void run() { final long threadId = Thread.currentThread().getId(); final String indent = "\t".repeat(colunmNumber); // Java 11. int computeTime = 2 + random.nextInt(8); System.out.println(indent + "t" + threadId + "," + computeTime); while (computeTime > 0) { try{TimeUnit.SECONDS.sleep(1);}catch(InterruptedException e){} --computeTime; System.out.println(indent + "t" + threadId + "," + computeTime); } } } // TaskC class for use with an ExecutorService thread pool with Future objects. import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.concurrent.Callable; public class TaskC implements Callable { private final Random random = new Random(); private final int colunmNumber; TaskC(int columnNumber) { this.colunmNumber = columnNumber; } @Override public Integer call() { final long threadId = Thread.currentThread().getId(); final String indent = "\t".repeat(colunmNumber); // Java 11. int computeTime = 2 + random.nextInt(8); System.out.println(indent + "t" + threadId + "," + computeTime); while (computeTime > 0) { try{ TimeUnit.SECONDS.sleep(1); }catch(InterruptedException e){} --computeTime; System.out.println(indent + "t" + threadId + "," + computeTime); } return computeTime; } }