The Artima Developer Community
Sponsored Link

Computing Thoughts
Too Much Threading
by Bruce Eckel
September 12, 2005
Summary
Threading has sucked down a lot of my life in the past few years. Aside from work on earlier versions of Thinking in Java, I clocked 8 months on the threading chapter in "Thinking in C++, Volume 2." I've already spent several months on the "Concurrency" Chapter in "Thinking in Java 4th edition." And now I'm back working on it again.

Advertisement

Because of all the changes in concurrency, the chapter has gotten big. At one point I thought of making it into a separate book. Even with the size that it is, I'm not able to cover all the new library components in java.util.concurrent.

I'm actually having to remove examples. Here's one that I just couldn't make into something appropriate; it was an attempt to use the Exchanger class, which I've decided is just a bit too obscure to include in the book.

However, I think I might get some mileage out of it by posting it here. First, to show you just how far threading has driven me, this example has degenerated into a simulation of a science fiction planet containing mythical creatures with completely questionable reasons for existence. I don't think they could have evolved this way, so perhaps it's an example of "unintelligent design."

More importantly, I'm not sure that I have or haven't synchronized properly or made necessary fields volatile (see the previous posting). At the time I wrote this I had been immersed in threading for long enough that I might have gotten it right -- after all, many of the new components in java.util.concurrent are designed to provide thread safety by themselves. But I figure the eyeballs and feedback will help me ensure that all the rest of the examples in the chapter are correct. I'm hoping that you don't just say "make everything volatile" or "synchronize all methods," but give me the argument for why a particular field or method should be modified, so I can make a clear case for it in the other examples in the book.

Here's the backstory for the example: Red Nibbins and Blue Nibbins try to fool each other into giving up their rample berries, which help them think. Basically, they’re confidence persons, albeit not very smart ones. The rample berries are carried in a special case, and the Nibbins trade cases, unopened, in the hope that the other case will have more berries than the one they have. When a Nibbin is asleep, a Berry Fairy may visit and add more Rample Berries to their case. As a Nibbin's side gains more rample berries than the other, they grow smarter and therefore have more cunning in acquiring more berries. Theoretically, one side should eventually win out over the other.

Note that this example is close to the end of the chapter, so issues such as task termination have already been thoroughly explained.


//: concurrency/Thwonk.java
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.*;
import static net.mindview.util.Print.*;

class RampleBerryCase implements Runnable {
  private static Random rand = new Random(47);
  private int berries = 3;
  public void run() {
    try {
      while(!Thread.interrupted()) {
        Thread.sleep(200 + rand.nextInt(1000));
        if(rand.nextInt(10) == 0) // 1 in 10 chance
          berries += 2; // Berry Fairy visits!
      }
    } catch(InterruptedException e) {
      // OK to terminate this way.
    }
  }
  public int numBerries() { return berries; }
}

class Nibbin implements Runnable {
  public enum Color { RED, BLUE };
  private Color color;
  static long counter = 0;
  long id = counter++;
  private static Random rand = new Random(47);
  private RampleBerryCase rampleBerryCase =
    new RampleBerryCase();
  private BerryBrokers berryBrokers ;
  // Construction must be done in steps to prevent
  // null pointers:
  public Nibbin(Color c, BerryBrokers brokers, Executor e){
    color = c;
    berryBrokers = brokers;
    e.execute(rampleBerryCase);
    e.execute(this);
  }
  Color getColor() { return color; }
  public void run() {
    try {
      while(!Thread.interrupted()) {
        int old = rampleBerryCase.numBerries();
        rampleBerryCase = berryBrokers.exchange(this);
        if(rampleBerryCase.numBerries() > old)
          print(this + "Thwonk!"); // (Hooray!)
        else
          print(this + "Blatz!"); // (Darn!)
      }
    } catch(InterruptedException e) {
      // OK to terminate this way.
    }
  }
  public RampleBerryCase getCase() {
    return rampleBerryCase;
  }
  public String toString() {
    return String.format(
      "%1$-4s Nibbin %2$-3d berries: %3$-3d ",
      color, id, rampleBerryCase.numBerries());
  }
}

class BerryBrokers {
  private static class BerryBroker {
    private Exchanger<RampleBerryCase> exchanger =
      new Exchanger<RampleBerryCase>();
    private ReentrantLock
      blueLock = new ReentrantLock(),
      redLock = new ReentrantLock();
    public RampleBerryCase offerBlue(RampleBerryCase old)
    throws InterruptedException {
      return exchanger.exchange(old);
    }
    public RampleBerryCase offerRed(RampleBerryCase old)
    throws InterruptedException {
      return exchanger.exchange(old);
    }
  }
  private ArrayList<BerryBroker> list =
    new ArrayList<BerryBroker>();
  public BerryBrokers(int size) {
    for(int i = 0; i < size; i++)
      list.add(new BerryBroker());
  }
  public RampleBerryCase exchange(Nibbin nibbin)
  throws InterruptedException {
    switch(nibbin.getColor()) {
      case RED:
        for(BerryBroker bb : list) {
          if(bb.redLock.tryLock())
            try {
              return bb.offerRed(nibbin.getCase());
            } finally {
              bb.redLock.unlock();
            }
        }
        // No red locks available. Return the old case:
        return nibbin.getCase();
      case BLUE:
        for(BerryBroker bb : list) {
          if(bb.blueLock.tryLock())
            try {
              return bb.offerBlue(nibbin.getCase());
            } finally {
              bb.blueLock.unlock();
            }
        }
        // No blue locks available. Return the old case:
        return nibbin.getCase();
      default:
        throw new RuntimeException("Not red or blue");
    }
  }
}

public class Thwonk {
  static int size = 100;
  public static void main(String[] args) throws Exception {
    if(args.length > 0) { // Optional argument
      int n = new Integer(args[0]);
      size = n > 0 ? n : size;
    }
    BerryBrokers brokers = new BerryBrokers(size/2);
    ExecutorService e = Executors.newCachedThreadPool();
    for(int i = 0; i < size; i++) {
      new Nibbin(Nibbin.Color.RED, brokers, e);
      new Nibbin(Nibbin.Color.BLUE, brokers, e);
    }
    Thread.sleep(5000);
    e.shutdownNow();
  }
} /* Output: (Sample)
RED  Nibbin 0   berries: 3   Blatz!
RED  Nibbin 0   berries: 3   Blatz!
BLUE Nibbin 1   berries: 3   Blatz!
RED  Nibbin 2   berries: 3   Blatz!
BLUE Nibbin 1   berries: 3   Blatz!
BLUE Nibbin 1   berries: 3   Blatz!
RED  Nibbin 4   berries: 3   Blatz!
...
*///:~

Here's just one issue that I've recently become aware of: in the Nibbin constructor, two tasks are started using the executor e that's passed to the constructor. I've recently learned that creating and starting a thread inside a constructor is problematic, because the constructor doesn't normally release the object lock until construction is complete (thus guaranteeing object consistency when threads are present -- the object is fully constructed before any threads can modify it), but if you start a thread within a constructor, the object lock is then released and the resulting thread can then modify the object before construction is finished. But would the same be true in this case, where a task is handed to an executor? It would be lovely if the executor guards you against such an issue; I already prefer executors to raw threads but this would make the argument for executors far more compelling.

I had made a note to myself to see if this example might be converted into the "beer game" (a business-school simulation of product distribution; see google), but I'm not sure if that's an easy conversion; it's probably a green-fields design instead. As interesting as I think it would be, time constrains will prevent the beer game from making it into the book.

Thanks for any comments.

Talk Back!

Have an opinion? Readers have already posted 14 comments about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Bruce Eckel adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Bruce Eckel (www.BruceEckel.com) provides development assistance in Python with user interfaces in Flex. He is the author of Thinking in Java (Prentice-Hall, 1998, 2nd Edition, 2000, 3rd Edition, 2003, 4th Edition, 2005), the Hands-On Java Seminar CD ROM (available on the Web site), Thinking in C++ (PH 1995; 2nd edition 2000, Volume 2 with Chuck Allison, 2003), C++ Inside & Out (Osborne/McGraw-Hill 1993), among others. He's given hundreds of presentations throughout the world, published over 150 articles in numerous magazines, was a founding member of the ANSI/ISO C++ committee and speaks regularly at conferences.

This weblog entry is Copyright © 2005 Bruce Eckel. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2017 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us