Thursday, June 30, 2011

Concurrency Pitfalls and More?

Read the A Dozen Concurrency Pitfalls a while back and began to search and asking for correct answers. Yes, there are some people posting too like here. So this is mainly note to myself.

Here are the question and answer

val stopMe = new Runnable {
  private var stop = false
  override def run() { while (!stop) doSomething(); println("Stopped") }
  def stopTask() { stop = true }
}
new Thread(stopMe).start()
Answer:
  • no synchronization, no guarantee that the value changed for "stop" will ever be seen
  •  stop is not volatile so jvm spec cant guarantee the changed stop value to be visible among threads
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
  }
}
Answer:
  •  if doSomething() call stopNow(), then...deadlock?
  • stop.set(false) invocation should be stop.set(true)
class BackgroundTask(iters: Int) extends Runnable {
  override def run() { for (i <- 1 to iters) doSomething() }
}
new Thread(new BackgroundTask(1000)).run()
println("Started backgroundTask")
Answer:
  • should call "start()" instead of "run()". I do that all the time
val items = new ConcurrentHashMap[String, Int]
 ...
val keys = items.keySet().toArray
Answer:
  • values when keySet() is called may not be the same when toArray is finished, therefore, result maybe unintended
  • 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
class Stack {
  private val myLock = "LOCK"  

  def push(newValue: Int) {
    myLock.synchronized {
      ...
    }
  }
}
Answer:
  • String constant is shared in Java, all Stack will use the same "LOCK"
  • "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)
class Stack {
  ...
  def push(newValue: Int) {
    new String("LOCK").synchronized {
      ...
    }
  }
}
Answer:
  • new.......every push call creates a new lock object......nothing is really synchronized with it
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)
  }
}
Answer:
  • everytime "reallocate()" is called the reference of "values" changes, therefore similar to "releasing" the lock unintentionally
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() }
  }
}
Answer:
  • should always do while-do instead of do-while
  • A do/while is used for the condition variable rather than just while. If cond is in a signaled state initially, the condition that size is zero may not be checked
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()
    }
  }
}
Answer:
  • out.close() might throw exception, causing myLock.unlock() to never get called
val queue = new ArrayBlockingQueue[String](10)
val button = new JButton("Start")
button.addActionListener(new ActionListener {
  override def actionPerformed(event: ActionEvent) {
    queue.put(event.toString)
  }
}
Answer:
  • if queue.put is blocked, as queue is full, the main UI thread will be blocked
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))
    }
  }
  ...
}
Answer:
  • since ChangeListener is implemented by other ppl, if they call addListener or removeListener in there listener then unexpected behavior might occur (in fireListeners)
  • should use CopyOnWriteArrayList in order to allow a listener to call add/remove listener when the event is being dispatched without causing a CME and/or deadlock
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() }
Answer:
  • simple....................because SimpleDateFormat is too simple to run in multi-thread.......lol. refer to JavaDoc

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)
    }
  }
  ...
}
Answer:
  •   synchronizes on this.array but waits on this = IllegalMonitorStateException


Some Other:

public class MyServlet implements Servlet{
    private Object something;

    public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException{
        this.something = request.getAttribute("something");
        doSomething();
    }

    private void doSomething(){
        this.something ...
    }
}
Answer:
  • Servlet is Singleton in a container. Mutating a instance field will result in unexpected behavior of other process on running this Servlet

Double-Checked Locking idiom


// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
  private Helper helper = null;
  public Helper getHelper() {
    if (helper == null) {
       synchronized(this) {
         if (helper == null) {
            helper = new Helper();
         }
       }
    }
    return helper;
  }
  // other functions and members...
}

Answer:
  • When thread A is creating a new instance of Helper, thread B may come in and see that "helper" is no longer null and uses the not-fully-instantiated instance

No comments:

Post a Comment