Skip to main content

A Dozen Concurrency Pitfalls

Posted by cayhorstmann on May 4, 2011 at 8:52 PM PDT

I needed some filler material for my lectures on concurrency. I googled around for Java concurrency pitfalls and came up with a nice mixture of golden oldies and new ones (at least new to me). I cleaned them up and translated them into Scala because that's what we use in the course. Here they are, for your puzzling pleasure.

style="float: right; margin-left: 1em;" width="211" height="239" />

Rules of the game:

  • For each of these, the question is “What could go wrong?”
  • Everything is meant to be properly imported
  • The code is meant to be in a “reasonable” context—any tricks are
    visible in the code that you see
  • Other threads, doSomething, and ... can do
    anything reasonable, i.e. not call System.exit(0) or use
    reflection to mutate state. But they can call methods on non-private
    variables
  1. val stopMe = new Runnable {
      private var stop = false
      override def run() { while (!stop) doSomething(); println("Stopped") }
      def stopTask() { stop = true }
    }
    new Thread(stopMe).start()
  2. class MyTask implements Runnable {
      private val done = new ArrayBlockingQueue[String](1)
      private val stop = new AtomicBoolean

      public void run() {
        while (!stop.get()) doSomething()
        done.put("DONE")
      }

      public void stopNow() {
        stop.set(false)
        done.take() // Wait until run completes
      }
    }
  3. class BackgroundTask(iters: Int) extends Runnable {
      override def run() { for (i <- 1 to iters) doSomething() }
    }
    new Thread(new BackgroundTask(1000)).run()
    println("Started backgroundTask")
  4. val items = new ConcurrentHashMap[String, Int]
    ...
    val keys = items.keySet().toArray
  5. class Stack {
      private val myLock = "LOCK" 

      def push(newValue: Int) {
        myLock.synchronized {
          ...
        }
      }
    }
  6. class Stack {
      ...
      def push(newValue: Int) {
        new String("LOCK").synchronized {
          ...
        }
      }
    }
  7. class Stack {
      private var values = new Array[Int](10)
      private var size = 0

      def push(newValue: Int) {
        values.synchronized {
          if (size > values.size) reallocate()
          values(size) = newValue
          size += 1
        }
      }
      ...
      private def reallocate() {
        values = values ++ new Array[Int](values.size)
      }
    }
  8. class Stack {
      private val myLock = new ReentrantLock
      private val cond = myLock.newCondition()
      ...
      def pop() = {
        myLock.lock()
        try {   
          do { cond.await() } while (size == 0)
          size -= 1
          values(size)
        } finally { myLock.unlock() }
      }
    }
  9. class Stack {
      private val myLock = new ReentrantLock
      private var values = new Array[Int](10)
      private var size = 0
      ...
      def write(fileName: String) {  
        myLock.lock()
        val out = new PrintWriter(fileName)
        try {
          out.println(size + " " + values.mkString(" "))
        } finally { 
          out.close()
          myLock.unlock()
        }
      }
    }
  10. val queue = new ArrayBlockingQueue[String](10)
    val button = new JButton("Start")
    button.addActionListener(new ActionListener {
      override def actionPerformed(event: ActionEvent) {
        queue.put(event.toString)
      }
    }
  11. class Model {
      private val myLock = new ReentrantLock
      private def withMyLock(block: => Unit) {
        myLock.lock(); try { block } finally { myLock.unlock() ; }
      }

      private val listeners = new ArrayBuffer[ChangeListener]
     
      def addListener(l: ChangeListener) { withMyLock { listeners += l } }
      def removeListener(l: ChangeListener) { withMyLock { listeners -= l } }
      def fireListeners() {
        withMyLock {
          for (l <- listeners) l.stateChanged(new ChangeEvent(this))
        }
      }
      ...
    }
  12. val queue = new ArrayBlockingQueue[String](10)
    val formatter = new SimpleDateFormat("MMM dd HH:mm:ss")
    class MyTask extends Runnable {
      override def run() {
        Object result = doSomething()
        queue.put(formatter.format(new Date) + " " + result)
      }
    }
    for (i <- 1 to 10) { new Thread(new MyTask).start() }
Related Topics >>

Comments

<p>You gonna teach actors again, I'm hoping? That was a lot ...

You gonna teach actors again, I'm hoping? That was a lot of fun, and certainly less prone to burning your laptop up :)
Glenn

<p>You forget this one which is fairly ...

You forget this one which is fairly common:

class Stack(val maxSize: Int) {
  private val array = new ArrayDeque[Int]

  def put(value: Int) {
    array.synchronized {
      while(array.size == maxSize) {
        wait()
      }
      array.add(value)
    }
  }
  ...
}

<p>&nbsp;1. stop is not volatile so jvm spec cant guarantee ...

1. stop is not volatile so jvm spec cant guarantee the changed stop value to be visible among threads
2. stop.set(false) invocation should be stop.set(true)
3. called run() - wont spawn new thread, should call start() - this spawns new thread
4. val keys = items.keySet().toArray has no sense if it is not run in keys.synchronized block - other threads may change items and toArray will throw ConcurrentModificationException
5. "LOCK" String is pointing to instance in JVM`s internal strings cache (somewhere in String class) - this synchronized method may cause much "wider" synchronization than it seems (global lock on all non-new "Lock" Strings)
6. locking on "always new objects" is not wise :)
7. reallocate method creates new instance of values variable. it means that synchronized monitors flies away and other threads are able to step inside values.synchronized {} block despite fact that the first thread didnt leave this block
8. this Scala snippet is unreadable to me, I know only plain old Java :)
9. new PrintWriter(fileName) called outside of try {} block - costructor used in snippet throws FileNotFoundException, so it can never release acquired lock
10. potentially blocking EventDispatchThread - not wise :)
11. dont see anything unusual to me, new ChangeEvent(this) may seem a little fishy but ReentrantLock is always open for thread, which owns the lock
12. SimpleDateFormat object is shared but it`s not thread safe, it should be cloned or new instance should be spawned before format
13. synchronizes on this.array but waits on this = IllegalMonitorStateException
Btw. Scalas generics in [] looks so misleading... why did they change that? o_O

Some additional answers: 8. The problem is the do { ... } ...

Some additional answers:

8. The problem is the do { ... } while () loop as it calls cond.await() at least once, even if size > 0.
11. It is not wise to invoke stateChanged on the listener while holding the lock, as you do not know how long this method takes. Moreover, if the listener removes itself from the model (or adds a new listner) then you get an exception (in Java you would get a ConcurrentModificationException)