Search code examples
javaandroidconcurrencystack-overflow

Java concurrency: synchronized statements/methods causing StackOverflowError exception


I'm stress-testing an event callback handling framework in Java which makes heavy use of concurrency where possible, and thus makes use of synchronized methods and statements when data races are a possibility. In an aptly named unit test called testDispatchWorstCase, anywhere from 100 to 500 threads will be launched concurrently, each of which calling methods with synchronized statements which utilize the monitor of a single controller object called MasterSemaphore. When testing with 100 threads, sometimes everything works perfectly, and sometimes I run into a stackoverflow exception in one or more of the synchronized statements and/or methods. When testing with 500 threads, I almost always run into a stackoverflow exception in one or more of the synchronized statements and/or methods.

MasterSemaphore provides the monitor used to lock the synchronized statements and also provides some of the event handling methods which are called to coordinate various threads' access to resources. Given that, I thought perhaps using a member Object of MasterSemaphore, one which had no other responsibilities, as the monitor provider would solve the problem-- unfortunately this only led to deadlock; hundreds of threads where stuck in the 'monitor' state in the eclipse's debugger's thread view and stayed that way forever. My questions are as follows:

  1. How does an object's monitor store and track references to the threads subject to this monitor's lock?

  2. Why did I run into a deadlock using a member object of MasterSemaphore for the monitor provider? Do I need to explicitly call wait...notifyAll within synchronizes statements?

  3. What might be causing the stackoverflow in synchronized statements when hundreds of threads are subject to one object's monitor lock?

The stack trace is:

01-02 07:47:44.910 27698 27842 E AndroidRuntime: FATAL EXCEPTION: HPCHead Spawned 
Thread #24

01-02 07:47:44.910 27698 27842 E AndroidRuntime: java.lang.StackOverflowError

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at   
java.lang.AbstractStringBuilder.append0(AbstractStringBuilder.java:147)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
java.lang.IntegralToString.convertInt(IntegralToString.java:209)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
java.lang.IntegralToString.appendInt(IntegralToString.java:173)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
java.lang.StringBuilder.append(StringBuilder.java:139)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
java.lang.Thread.toString(Thread.java:1098)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2524)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at     
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler
.onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterEventHandler$EventIDFreedHandler.
onUpdateNeeded(MasterEventHandler.java:221)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.CallbackDataRecord.
postToQueueHead(CallbackDataRecord.java:107)

01-02 07:47:44.910 27698 27842 E AndroidRuntime:    at 
com.hdradio.hdradiomanagerjc.hpc.MasterSemaphore.
onFinishedEventProcessing(MasterSemaphore.java:1219)

Solution

  • Answers to your questions:

    1. Here's an article that describes how synchronization using object monitors works

    2. I'm not sure why you ended up with a deadlock. I'd need to see a lot more code to be able to help with that. As I said in my earlier comment, though, you don't need to use wait'/notifyAllwhen usingsynchronized. The JVM handles all the locking and waiting automatically for you. Of course, this doesn't mean that you can't program yourself into a deadlock situation (that's actually pretty easy to do). Addingnotify()` calls won't undo a deadlock though.

    3. So the stack overflow problem is coming from a recursive call stack that looks like this:

      ...

      MasterSemaphore.onFinishedEventProcessing(MasterSemaphore.java:1219) MasterSemaphore.addRegistrantForCallback_PrefabEventHandling(MasterSemaphore.java:2597) MasterEventHandler$EventIDFreedHandler.onUpdateNeeded(MasterEventHandler.java:221) CallbackDataRecord.postToQueueHead(CallbackDataRecord.java:107) MasterSemaphore.onFinishedEventProcessing(MasterSemaphore.java:1219)

    So onFinishedEventProcessing() is being called recursively and this is causing the stack overflow. You should have a look and see why that is happening (add some logging if needed).