Critical Section Problems
Here I wish to show you some of my DIY demo programs demonstrating how Critical Section Problems can be solved by using Peterson's Algorithm, Spinlocks as well as Semaphores.
This is a demo program of Peterson's Algorithm. Here two clients send messages to their corresponding server threads, and the two server threads print messages on the same terminal without interference.
1 import java.util.concurrent.atomic.*; 2 import java.util.*; 3 import java.io.*; 4 5 class Server { 6 /** 7 * This class simulates a Server System that can 8 * service two Client Threads simultaneously 9 */ 10 private class Task extends Thread { 11 /** 12 * This class simulates a Server Thread 13 */ 14 private int id, cnt; 15 private Scanner in; 16 17 public Task(int id,PipedOutputStream pout) throws IOException { 18 this.id = id; 19 in = new Scanner(new PipedInputStream(pout)); 20 } 21 public void run() { 22 /* Print the messages from the client */ 23 try { 24 while (!getAndPrintMsg()); 25 } catch (Exception e) { 26 System.err.println("Error 2: "+e); 27 } 28 } 29 private boolean getAndPrintMsg() throws IOException { 30 /* 31 * Implementation of Peterson's Algorithm: 32 */ 33 String str = in.nextLine(); 34 if (!str.equals("over")) { 35 flag[id].set(true); 36 turn.set(1-id); 37 while (flag[1-id].get()&&turn.get()==1-id); 38 System.out.println("\t"+this+" #"+(++cnt)+": \t"+str); // critical section 39 flag[id].set(false); 40 return false; 41 } else { 42 return true; 43 } 44 } 45 public String toString() { 46 return "Server " + (id+1); 47 } 48 } 49 50 private AtomicBoolean[] flag; 51 private AtomicInteger turn; 52 53 public Server(PipedOutputStream pout1,PipedOutputStream pout2) { 54 // initialization of flag and turn 55 flag = new AtomicBoolean[2]; 56 for (int i=0;i<2;i++) { 57 flag[i] = new AtomicBoolean(false); 58 } 59 turn = new AtomicInteger(0); 60 try { 61 (new Task(0,pout1)).start(); 62 (new Task(1,pout2)).start(); 63 } catch (IOException e) { 64 e.printStackTrace(); 65 } 66 } 67 } 68 69 class Client extends Thread { 70 /** 71 * This Class simulates a Client Thread 72 */ 73 private int id; 74 private PipedOutputStream pout; 75 76 public Client(int id,PipedOutputStream pout) { 77 this.id = id; 78 this.pout = pout; 79 start(); 80 } 81 public void run() { 82 try { 83 for (int i=0;i<100;i++) { 84 String str = this+" obtains the console\n"; 85 pout.write(str.getBytes()); 86 } 87 pout.write("over\n".getBytes()); 88 } catch (Exception e) { 89 System.err.println("Error 1: "+e); 90 } 91 } 92 public String toString() { 93 return "client "+(id+1); 94 } 95 } 96 97 public class Main { 98 99 public static void main(String[] args) { 100 PipedOutputStream pout1 = new PipedOutputStream(); 101 PipedOutputStream pout2 = new PipedOutputStream(); 102 try { 103 new Server(pout1, pout2); 104 new Client(0,pout1); 105 new Client(1,pout2); 106 } catch (Exception e) { 107 System.err.println("Error 0: "+e); 108 } 109 } 110 }
The following program implements Spinlock by using Java AtomicBoolean class:
1 import java.util.concurrent.atomic.*; 2 3 public class Main { 4 /** 5 * This class implements Spinlock Algorithm 6 */ 7 private static class Task extends Thread { 8 /** 9 * This class implements a task thread 10 */ 11 private int idx; 12 13 public Task(int idx) { 14 this.idx = idx; 15 start(); 16 } 17 public void run() { 18 LOOP: 19 for (int i=1;i<=3;i++) { 20 while (wait[idx].get()&&lock.getAndSet(true)); 21 System.out.println("\t"+this+" ("+i+")\tin Critical Section"); 22 int next = idx; 23 for (int j=1;j<NUM;j++) { 24 next = (next+1)%NUM; 25 if (wait[next].get()) { 26 wait[next].set(false); 27 continue LOOP; 28 } 29 } 30 lock.set(false); 31 } 32 } 33 public String toString() { 34 return "Task "+(idx+1); 35 } 36 } 37 38 private static final int NUM = 50; 39 private static AtomicBoolean lock; 40 private static AtomicBoolean[] wait; 41 42 public static void main(String[] args) { 43 lock = new AtomicBoolean(false); 44 wait = new AtomicBoolean[NUM]; 45 for (int i=0;i<NUM;i++) { 46 wait[i] = new AtomicBoolean(false); 47 } 48 Task[] task = new Task[NUM]; 49 for (int i=0;i<NUM;i++) { 50 task[i] = new Task(i); 51 } 52 for (int i=0;i<NUM;i++) { 53 try { 54 task[i].join(); 55 } catch (InterruptedException e) { 56 e.printStackTrace(); 57 } 58 } 59 } 60 }
As the first demo of Java Semaphores, the following program solves the classical Sleeping Barber Problem.
1 import java.util.concurrent.atomic.*; 2 import java.util.concurrent.*; 3 import java.util.*; 4 5 public class Main { 6 /** 7 * This class simulates Sleeping Barber Problem 8 */ 9 private static class Barber extends Thread{ 10 /** 11 * This class simulates a barber 12 */ 13 14 public Barber() { 15 start(); 16 } 17 public void run() { 18 try { 19 while(num.get()>0) { 20 task.acquire(); 21 service.release(); 22 TimeUnit.MILLISECONDS.sleep(servTime); 23 } 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } 27 } 28 } 29 private static class Customer extends Thread{ 30 /** 31 * This class simulates a customer 32 */ 33 private int idx; 34 35 public Customer(int idx) { 36 this.idx = idx; 37 start(); 38 } 39 public void run() { 40 try { 41 mutex.acquire(); 42 if (chair>0) { 43 chair--; 44 mutex.release(); 45 } else { 46 synchronized(this) { 47 System.out.println("\t"+this+" leaves"); 48 } 49 mutex.release(); 50 num.getAndDecrement(); 51 return; 52 } 53 synchronized(this) { 54 System.out.println("\t"+this+" sits and waits"); 55 } 56 task.release(); 57 service.acquire(); 58 num.getAndDecrement(); 59 synchronized(this) { 60 System.out.println("\t"+this+" is served"); 61 } 62 TimeUnit.MILLISECONDS.sleep(servTime); 63 mutex.acquire(); 64 chair++; 65 mutex.release(); 66 } catch (InterruptedException e) { 67 e.printStackTrace(); 68 } 69 } 70 public String toString() { 71 return "Customer # "+idx+"\t"; 72 } 73 } 74 75 private static Semaphore mutex; 76 private static int chair = 10; 77 private static AtomicInteger num; 78 private static Semaphore task; 79 private static Semaphore service; 80 private static int servTime = 25; 81 private static int custTime = 20; 82 83 public static void main(String[] argds) { 84 mutex = new Semaphore(1); 85 task = new Semaphore(0); 86 service = new Semaphore(0); 87 num = new AtomicInteger(50); 88 System.out.println("Number of Chairs:\t"+chair); 89 System.out.println("Number of Customers:\t"+num); 90 System.out.println(); 91 int numVal = num.get(); 92 Barber barber = new Barber(); 93 Customer[] cust = new Customer[numVal]; 94 try { 95 for (int i=0;i<numVal;i++) { 96 cust[i] = new Customer(i); 97 int t = (new Random()).nextInt(custTime); 98 TimeUnit.MILLISECONDS.sleep(t); 99 } 100 barber.join(); 101 for (int i=0;i<numVal;i++) { 102 cust[i].join(); 103 } 104 } catch (Exception e) { 105 e.printStackTrace(); 106 } 107 } 108 }
The second example simulates Smoking Lounge Problem, where a smoker cannot smoke when a non-smoker stays in the lounge, and a non-smoker will not enter the lounge as long as there is a smoker smoking in the lounge.
1 import java.util.concurrent.atomic.*; 2 import java.util.concurrent.*; 3 import java.util.*; 4 5 public class Main { 6 /** 7 * This class simulates Lounge Problem 8 */ 9 private static class Smoker extends Thread{ 10 /** 11 * This class simulates a smoker 12 */ 13 private int idx; 14 15 public Smoker(int idx) { 16 this.idx = idx; 17 start(); 18 } 19 public void run() { 20 try { 21 System.out.println("\t"+this+" comes in"); 22 wait.acquire(); 23 if (smkCnt.getAndIncrement()==0) { 24 semp.acquire(); 25 } 26 wait.release(); 27 System.out.println("\t"+this+" smokes"); 28 int t = (new Random()).nextInt(10); 29 TimeUnit.MILLISECONDS.sleep(t); 30 System.out.println("\t"+this+" goes out"); 31 if (smkCnt.decrementAndGet()==0) { 32 semp.release(); 33 } 34 } catch (Exception e) { 35 e.printStackTrace(); 36 } 37 } 38 public String toString() { 39 return "Smoker # "+idx+" "; 40 } 41 } 42 private static class Nonsmk extends Thread { 43 /** 44 * This class simulates a non-smoker 45 */ 46 private int idx; 47 48 public Nonsmk(int idx) { 49 this.idx = idx; 50 start(); 51 } 52 public void run() { 53 try { 54 wait.acquire(); 55 if (nonCnt.getAndIncrement()==0) { 56 semp.acquire(); 57 } 58 wait.release(); 59 System.out.println("\t"+this+" comes in"); 60 int t = (new Random()).nextInt(10); 61 TimeUnit.MILLISECONDS.sleep(t); 62 System.out.println("\t"+this+" goes out"); 63 if (nonCnt.decrementAndGet()==0) { 64 semp.release(); 65 } 66 } catch (Exception e) { 67 e.printStackTrace(); 68 } 69 } 70 public String toString() { 71 return "NonSmk # "+idx+" "; 72 } 73 } 74 75 private static final int num = 100; 76 private static Semaphore wait; 77 private static Semaphore semp; 78 private static AtomicInteger smkCnt; 79 private static AtomicInteger nonCnt; 80 81 static { 82 wait = new Semaphore(1); 83 semp = new Semaphore(1); 84 smkCnt = new AtomicInteger(0); 85 nonCnt = new AtomicInteger(0); 86 } 87 public static void main(String[] args) { 88 int smkNum=0, nonNum=0; 89 Thread[] thread = new Thread[num]; 90 try { 91 for (int i=0;i<num;i++) { 92 int r = (new Random()).nextInt(5); 93 if (r<3) { 94 thread[i] = new Nonsmk(nonNum++); 95 } else { 96 thread[i] = new Smoker(smkNum++); 97 } 98 int t = (new Random()).nextInt(5); 99 TimeUnit.MILLISECONDS.sleep(t); 100 } 101 for (int i=0;i<num;i++) { 102 thread[i].join(); 103 } 104 } catch (Exception e) { 105 e.printStackTrace(); 106 } 107 } 108 }
At the end of this article, I wish to generalize the smoking lounge problem above: given num different types of threads, as well as all pairs of i and j such that thread type i and type j cannot get access to the critical section together (possibly i==j), describe the behavior of each type of thread so that all of them can work orderly without deadlock or starvation.
The input of the program should comprise: (1) one integer num indicating the number of thread types; (2) num integers indicating the number of each type of threads; (3) one integer N indicating the number of following input pairs; (4) N integer pairs representing the thread types that cannot coexist in the critical section.
As a matter of fact, this problem is a generalization of many classical problems such as Reader-Writer Problem, Baboon Crossing Problem, and Search/Insert/Delete Problem. For example, we assume there are 100 threads to search, 50 threads to insert and 20 threads to delete, then the input should be:
100 20 0 2 1 2 2
The following program is a naive general solution that I have come up with:
1 import java.util.concurrent.atomic.*; 2 import java.util.concurrent.*; 3 import java.util.*; 4 import java.io.*; 5 6 class Input { 7 private Scanner in; 8 private StringTokenizer tok; 9 10 public Input() { 11 in = new Scanner(new BufferedInputStream(System.in)); 12 } 13 public String nextString() { 14 while (tok==null||!tok.hasMoreTokens()) { 15 tok = new StringTokenizer(in.nextLine()); 16 } 17 return tok.nextToken(); 18 } 19 public int nextInt() { 20 while (tok==null||!tok.hasMoreTokens()) { 21 tok = new StringTokenizer(in.nextLine()); 22 } 23 return Integer.parseInt(tok.nextToken()); 24 } 25 public double nextDouble() { 26 while (tok==null||!tok.hasMoreTokens()) { 27 tok = new StringTokenizer(in.nextLine()); 28 } 29 return Double.parseDouble(tok.nextToken()); 30 } 31 public void close() { 32 in.close(); 33 } 34 } 35 36 class CSP { 37 private class Task extends Thread { 38 private int type; 39 40 public Task(int type) { 41 this.type = type; 42 } 43 public void run() { 44 try { 45 sempAcquire(); 46 System.out.println("\t"+this+" is working."); 47 sempRelease(); 48 } catch (Exception e) { 49 System.err.println("TASK RUN Error: "+e); 50 } 51 } 52 private void sempAcquire() throws InterruptedException { 53 wait.acquire(); 54 if (mutex[type][type]) { 55 semp[type][type].acquire(); 56 } 57 if (cnt[type].getAndIncrement()==0){ 58 for (int i=0;i<type;i++) { 59 if (mutex[type][i]) { 60 semp[type][i].acquire(); 61 } 62 } 63 for (int i=type+1;i<num;i++) { 64 if (mutex[i][type]) { 65 semp[i][type].acquire(); 66 } 67 } 68 } 69 wait.release(); 70 } 71 private void sempRelease() { 72 if (cnt[type].getAndDecrement()==1){ 73 for (int i=num-1;i>type;i--) { 74 if (mutex[i][type]) { 75 semp[i][type].release(); 76 } 77 } 78 for (int i=type-1;i>=0;i--) { 79 if (mutex[type][i]) { 80 semp[type][i].release(); 81 } 82 } 83 } 84 if (mutex[type][type]) { 85 semp[type][type].release(); 86 } 87 } 88 public String toString() { 89 int idxVal = idx[type].getAndIncrement(); 90 return "Task "+type+" #"+idxVal; 91 } 92 } 93 94 private int num; 95 private Task[][] taskArr; 96 private AtomicInteger[] cnt; 97 private AtomicInteger[] idx; 98 private Semaphore wait; 99 private Semaphore[][] semp; 100 private boolean mutex[][]; 101 102 public CSP(int num) { 103 this.num = num; 104 taskArr = new Task[num][]; 105 cnt = new AtomicInteger[num]; 106 idx = new AtomicInteger[num]; 107 for (int i=0;i<num;i++) { 108 cnt[i] = new AtomicInteger(0); 109 idx[i] = new AtomicInteger(0); 110 } 111 try { 112 wait = new Semaphore(1); 113 semp = new Semaphore[num][]; 114 mutex = new boolean[num][]; 115 for (int i=0;i<num;i++) { 116 semp[i] = new Semaphore[i+1]; 117 mutex[i] = new boolean[i+1]; 118 } 119 } catch (Exception e) { 120 System.err.println("CSP INIT Error: "+e); 121 } 122 } 123 public void genThread(int type,int n) { 124 taskArr[type] = new Task[n]; 125 for (int i=0;i<n;i++) { 126 taskArr[type][i] = new Task(type); 127 } 128 } 129 public void setMutex(int i,int j) { 130 if (j>i) { 131 i += j; 132 j = i-j; 133 i -= j; 134 } 135 mutex[i][j] = true; 136 try { 137 semp[i][j] = new Semaphore(1); 138 } catch (Exception e) { 139 System.err.println("CSP SETMUTEX Error: "+e); 140 } 141 } 142 public void start() { 143 class Pair { 144 public int type,idx; 145 public Pair(int i,int j) { 146 type = i; 147 idx = j; 148 } 149 } 150 List<Pair> q = new LinkedList<Pair>(); 151 for (int i=0;i<num;i++) { 152 for (int j=0;j<taskArr[i].length;j++) { 153 q.add(new Pair(i,j)); 154 } 155 } 156 Collections.shuffle(q,new Random()); 157 while (!q.isEmpty()) { 158 Pair item = q.remove(0); 159 taskArr[item.type][item.idx].start(); 160 } 161 } 162 public void join() { 163 try { 164 for (int i=0;i<num;i++) { 165 for (int j=0;j<taskArr[i].length;j++) { 166 taskArr[i][j].join(); 167 } 168 } 169 } catch (Exception e) { 170 System.err.println("CSP JOIN Error: "+e); 171 } 172 } 173 } 174 175 176 public class Main { 177 178 public static void main(String[] args) { 179 Input in = new Input(); 180 int n = in.nextInt(); 181 CSP csp = new CSP(n); 182 for (int i=0;i<n;i++) { 183 csp.genThread(i,in.nextInt()); 184 } 185 int m = in.nextInt(); 186 for (int i=0;i<m;i++) { 187 csp.setMutex(in.nextInt(),in.nextInt()); 188 } 189 in.close(); 190 System.out.println("Critical Section Problem:"); 191 csp.start(); 192 csp.join(); 193 } 194 }
References:
1. Nutt, Gary. Operating Systems: A Modern Perspective[M].北京:机械工业出版社, 2004-02
2. Eckel, Bruce. Thinking in Java[M]. 北京:机械工业出版社, 2007-06