How to avoid the race condition found in the following code?

Consider a snippet taken from here :

// event
public class Event { }
// An Event Listener
public interface EventListener {
        public void onEvent(Event e);
}

// inner class instances contain a hidden reference to the enclosing instance
public class ThisEscape {
    private final int num;

    public ThisEscape(EventSource source) {
        source.registerListener(new EventListener() {

            @Override
            public void onEvent(Event e) {
                doSomething(e);
            }
        });
        num = 42;
    }

    private void doSomething(Event e) {
        if (num != 42) {
            System.out.println("Race condition detected at " + new Date());
        }
    }
}
// event source

public class EventSource extends Thread {
    private final BlockingQueue<EventListener> listeners = 
                                new LinkedBlockingQueue<EventListener>();

    public void run() {
        while (true) {
            try {
                listeners.take().onEvent(null);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

    public void registerListener(EventListener eventListener) {
        listeners.add(eventListener);
    }
}


// testing the conditions

public class ThisEscapeTest {
    public static void main(String[] args) {
        EventSource es = new EventSource();
        es.start();
        while (true) {
            new ThisEscape(es);
        }
    }
}

For consolidation, we have 2 threads

// Main Thread
// Event Source

In the event source stream there is a BlockingQueue to hold the EventListener. In the execution method for the same thread

The consumed EventSource thread continues to list objects from the lock queue and processes them. If the same thread tries to pull an object from an empty queue, the same thread is blocked until producing thread(Main thread) puts the object in the queue.

2 () , - - timimg, 2 , EventSource , num!= 2 , , .

source.registerListener(new EventListener() {   // OPERATION 1      
    @Override
    public void onEvent(Event e) {
        doSomething(e);
    }
    });
    num = 42;                                       // OPERATION 2
}

, , .

( ), doSomething() ( EventSource) , , 2 . ?

 public ThisEscape(EventSource source) {
     synchronized(this){                // SYNCHRONISED
         source.registerListener(new EventListener() {
            @Override
            public void onEvent(Event e) {
                doSomething(e);
            }
         });
    num = 42;
    }
}

- doSomething(), , ?

-, , , . . final ( private final int num = 42)?

+4
3

, this - .

; factory.

public static ThisEscape newInstance(EventSource source){
   final ThisEscape instance = new ThisEscape();
   source.registerListener(new EventListener() {

        @Override
        public void onEvent(Event e) {
            instance.doSomething(e);
        }
    }
   return instance;
}
+4

registerListener() num, , , . , num , , .

, num

public static class ThisEscape {
    private final int num = 42;

    public ThisEscape(EventSource source) {
        source.registerListener(e -> doSomething(e));
    }
    //...
}

, registerListener()

public static class ThisEscape {
    private volatile int num;

    public ThisEscape(EventSource source) {
        num = 42;
        source.registerListener(e -> doSomething(e));
    }
    //...
}

: @AndyTurner @ShirgillFarhanAnsari , .

+1

this. , num , , .

, 2 :

  • registerListener
  • num = 42

Now the callback onEventcan be called shortly after the return, registerListeneror even before it returns, since it is asynchronous. In any case, before or after num = 42 . It needs to be synchronized or properly ordered.

0
source

Source: https://habr.com/ru/post/1650297/


All Articles