package net.proteanit.demo;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
enum AccessType {
   READER,
   WRITER;
}
/**
* This is a simple game of chance to demonstrate ReadWrite locks in Java: a
* writer acquires a write lock and rewrites a List by shuffling it.
* If this ends up being in the correct order, the next reading thread to read
* the data wins the game
*
* @author Charles Johnson
* @version 1.0
 */
public class LockGame {
   private final ReentrantReadWriteLock rwl;
   private final List<String> data;
   private final int correctOrder;
   private final Lock r;
   private final Lock w;
   private final Logger log;
   private ExecutorService threads;
   private volatile boolean playerHasWon;
   public LockGame() {
       log = Logger.getLogger(LockGame.class);
       rwl = new ReentrantReadWriteLock();
       data = new ArrayList<String>();
       data.add("alpha");
       data.add("beta");
       data.add("gamma");
       correctOrder = data.hashCode();
       r = rwl.readLock();
       w = rwl.writeLock();
   }
   private void start() {
       final int SIZE = 16;
       List<Callable<Void>> workers = new ArrayList<Callable<Void>>(SIZE);
       for (int i = 0; i < SIZE; i++) {
           workers.add(new ReaderWriter());
       }
       threads = Executors.newFixedThreadPool(SIZE, new PrettyPrintThreadFactory(99, "thread-"));
       try {
           if (log.isDebugEnabled()) {
               log.debug("Invoking all players...");
           }
           threads.invokeAll(workers);
       } catch (InterruptedException e) {
           e.printStackTrace();
       } catch (RejectedExecutionException e) {
           // Ignore this - some thread has won
       } finally {
           // Another thread might have already called this
           stop();
       }
   }
   private void stop() {
       if (!threads.isShutdown()) {
           threads.shutdownNow();
       }
       if (!playerHasWon && log.isDebugEnabled()) {
           log.debug("There was no winner on that run");
       }
   }
   private void changeData() {
       try {
           w.lock();
           if (log.isDebugEnabled()) {
               log.debug(String.format("Acquired lock in changeData() at %d", System.currentTimeMillis()));
           }
           Collections.shuffle(data);
           if (log.isDebugEnabled()) {
               log.debug(String.format("Data changed - now %s", data.toString()));
           }
           try { Thread.sleep((int)(Math.random() * 1000)); } catch(InterruptedException e) { /* ignore it */ }
       } finally {
           w.unlock();
           if (log.isDebugEnabled()) {
               log.debug(String.format("Released lock in changeData() at %d", System.currentTimeMillis()));
           }
       }
   }
   private void readData() {
       try {
           // We can't be sure that all threads can be closed down
           // after a single winner emerges so we make the boolean check
           if (playerHasWon) {
               return;
           }
           r.lock();
           if (log.isDebugEnabled()) {
               log.debug(String.format("Acquired lock in readData() at %d", System.currentTimeMillis()));
           }
           if (log.isDebugEnabled()) {
               log.debug(String.format("Reading data - now %s", data.toString()));
           }
           if (data.hashCode() == correctOrder) {
               if (log.isDebugEnabled()) {
                   log.debug("I'VE WON! Data now correct");
               }
               playerHasWon = true;
               stop();
           }
           else {
               try { Thread.sleep((int)(Math.random() * 1000)); } catch(InterruptedException e) { /* ignore it */ }
           }
       } finally {
           r.unlock();
           if (log.isDebugEnabled()) {
               log.debug(String.format("Released lock in readData() at %d", System.currentTimeMillis()));
           }
       }
   }
   public static void main(String[] args) {
       LockGame lg = new LockGame();
       lg.changeData();
       lg.start();
   }
   private class ReaderWriter implements Callable<Void> {
       private AccessType access;
       public ReaderWriter() {
           // Roughly 1 in 5 chance of being a writer, else reader
           access = (Math.random() > 0.8) ? AccessType.WRITER : AccessType.READER;
       }
       public Void call() {
           if (log.isDebugEnabled()) {
               log.debug(String.format("This ReaderWriter is of type %s",
                       access));
           }
           switch (access) {
           case WRITER:
               changeData();
               return null;
           case READER:
               readData();
               return null;
           default:
               return null;
           }
       }
   }
}
The log file produced gives us some interesting information, but first we'll do a little processing on it:
| 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: | 14:43:50,957 [main] Acquired lock in changeData() at 1197902630929 14:43:51,486 [main] Released lock in changeData() at 1197902631485 14:43:51,567 [thread-00] Acquired lock in changeData() at 1197902631566 14:43:51,869 [thread-00] Released lock in changeData() at 1197902631869 14:43:51,872 [thread-01] Acquired lock in readData() at 1197902631871 14:43:51,876 [thread-03] Acquired lock in readData() at 1197902631875 14:43:52,070 [thread-03] Released lock in readData() at 1197902632069 14:43:52,636 [thread-01] Released lock in readData() at 1197902632633 14:43:52,640 [thread-04] Acquired lock in changeData() at 1197902632637 14:43:53,626 [thread-04] Released lock in changeData() at 1197902633621 14:43:53,628 [thread-06] Acquired lock in readData() at 1197902633627 14:43:53,631 [thread-08] Acquired lock in readData() at 1197902633630 14:43:53,749 [thread-06] Released lock in readData() at 1197902633749 14:43:54,322 [thread-08] Released lock in readData() at 1197902634321 14:43:54,323 [thread-07] Acquired lock in changeData() at 1197902634323 14:43:55,310 [thread-07] Released lock in changeData() at 1197902635305 14:43:55,312 [thread-09] Acquired lock in readData() at 1197902635311 14:43:55,315 [thread-02] Acquired lock in readData() at 1197902635314 14:43:55,493 [thread-09] Released lock in readData() at 1197902635493 14:43:55,514 [thread-02] Released lock in readData() at 1197902635509 14:43:55,516 [thread-10] Acquired lock in changeData() at 1197902635516 14:43:55,554 [thread-10] Released lock in changeData() at 1197902635553 14:43:55,556 [thread-05] Acquired lock in readData() at 1197902635555 14:43:55,561 [thread-12] Acquired lock in readData() at 1197902635559 14:43:55,918 [thread-05] Released lock in readData() at 1197902635917 14:43:56,546 [thread-12] Released lock in readData() at 1197902636545 14:43:56,547 [thread-13] Acquired lock in changeData() at 1197902636547 14:43:57,118 [thread-13] Released lock in changeData() at 1197902637117 14:43:57,119 [thread-14] Acquired lock in readData() at 1197902637119 14:43:57,122 [thread-11] Acquired lock in readData() at 1197902637121 14:43:57,124 [thread-15] Acquired lock in readData() at 1197902637123 14:43:57,394 [thread-14] Released lock in readData() at 1197902637393 14:43:57,534 [thread-15] Released lock in readData() at 1197902637533 14:43:58,010 [thread-11] Released lock in readData() at 1197902638009 | 
The important thing to note is that in places like lines 9-10 and 21-22 the write thread enters and exits in an orderly fashion with no competition.
In contrast, in place like lines 5-8, concurrent threads co-exist happily when only reading is being done. 'thread-01' gets a read lock in line 5, followed by 'thread-03' before thread-01 has released its lock.
No comments:
Post a Comment