1

I would like to know how a listener should let the parent thread know that it has been called. (Basically I'm trying to force an asynchronous call to be synchronous.) My example includes Android calls, but it's more a general synchronization question...

The method myMethod() requests that a SoundPool load a sound and notify a listener when the call is complete. I would like for myMethod() to wait until the listener has been called before returning. The code I came up with is:

void myMethod() {
    SoundPool soundPool = ...;
    final Lock object = new Object();

    soundPool.setOnLoadCompleteListener(new OnLoadCompleteListener(){
        @Override
        public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
            synchronized(lock) {
                lock.notify();
            }
        }});
    synchronized(lock) {
        int soundId = MediaUtil.loadSoundPool(soundPool, ...); // returns immediately
        lock.wait();
    }
}

(I left out try/catch for simplicity, although it's in the real code.) Normally, I'd also have a flag when communicating between two threads, but I can't use a local variable for this purpose, since it would have to be final to be accessible from the anonymous class, and an instance variable seems like an abstraction violation (and would have to be volatile).

Is the above code safe and/or is there a better way to solve the problem?

Ellen Spertus
  • 6,576
  • 9
  • 50
  • 101

2 Answers2

2

Many options to accomplish what you need. Here is one way to do it using wait/notify. The isNotified is here to prevent a hang in the case where onLoadComplete() is called by the same thread that calls setListener(). The class MonitorXX is here to make it easier to debug when you are looking at thread dumps from large applications. Add error/exception/timeOut checking as necessary.

static class Monitor42{boolean isNotified=false;} 
static Monitor42 monitor = new Monitor42(); 
void myMethod() { 
    synchronized(monitor) {
        monitor.isNotified=false;
        xxx.setListener(new Listener(){ 
        public void onLoadComplete() { 
            synchronized(monitor) {
                monitor.isNotified=true;
                monitor.notify(); 
            } 
        }}); 
        if (!monitor.isNotified) monitor.wait(someTimeValueMS);
        monitor.isNotified=false;
    } 
} 
Java42
  • 7,628
  • 1
  • 32
  • 50
1

Basically I'm trying to force an asynchronous call to be synchronous.

The canonical way to do this is exactly what you are doing: trigger a notification at the end of the asynchronous work in the background thread and wait for the notification in the main thread.

but I can't use a local variable for this purpose, since it would have to be final to be accessible from the anonymous class

The simple trick to bypass this restriction is to use an final Boolean[] flag = new Boolean[1]; flag[0] = false.

Miserable Variable
  • 28,432
  • 15
  • 72
  • 133
  • Thanks. Two follow-up questions: 1. Is it better to include the flag rather than just using wait/notify? 2. Would I have to make flag volatile (or synchronize on it) to ensure that the two threads see the same value? – Ellen Spertus Mar 20 '12 at 22:46
  • `wait/notify` is or its modern alternative is probably better because any other approach will require a `sleep`. See http://stackoverflow.com/questions/4912165/whats-the-best-alternative-to-wait-notify-for-low-level-synchronization. If `sleep` is fine, [AtomicBoolean](http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicBoolean.html) is better – Miserable Variable Mar 20 '12 at 23:16