How can I make sure a method is only called once by multiple threads?

后端 未结 5 1571
孤城傲影
孤城傲影 2020-12-17 17:46

I have the following structure:

public void someMethod(){  
   //DO SOME STUFF
   try{  
    doSomeProcessing();  
   }  
   catch (Exception e){  
                


        
相关标签:
5条回答
  • 2020-12-17 18:19

    We wrote a library that includes a utility lazily load/call a method. It guarantees single usage semantics and preserves any thrown exceptions as you'd expect.

    Usage is simple:

    LazyReference<Thing> heavyThing = new LazyReference<Thing>() {
      protected Thing create() {
        return loadSomeHeavyData();
      }
    };
    
    public void someMethod(){  
      //DO SOME STUFF
      try{  
        doSomeProcessing();  
      }  
      catch (Exception e){  
        heavyThing.get();  
        doSomeProcessing();      
      }    
    }  
    

    All threads block on the get() and wait for the producer thread (the first caller) to complete.

    0 讨论(0)
  • 2020-12-17 18:25

    Using the synchronized keyword:

    public synchronized void someMethod(){  
        //doStuff
    }
    

    You assure that only one thread enters at a time.

    To assure that the method is called only once, there is no special language feature; you could create a static variable of type boolean which is set to true by the first thread entering the method. When calling the method always check that flag:

    public class MyClass {
        private static boolean calledMyMethod;
    
        public synchronized void someMethod() {
            if(calledMyMethod) { 
                return;
            } else {
                calledMyMethod = true;
                //method logic
            }           
        }
    } 
    
    0 讨论(0)
  • 2020-12-17 18:26

    Your loadSomeHeavyData method could use a blocking mechanism to make all threads wait until it has finished its update, but only let one of them actually do the update:

    private final AtomicBoolean updateStarted = new AtomicBoolean();
    private final CountDownLatch updateFinished = new CountDownLatch(1);
    
    public void loadSomeHeavyData() {
        if (updateStarted.compareAndSet(false, true)) {
            //do the loading
            updateFinished.countDown();
        } else {
            //update already running, wait
            updateFinished.await();
        }
    }
    

    Note my assumptions:

    • you want all the threads to wait until the loading completes so they can call doSomeProcessing a second time with updated data
    • you only call loadSomeHeavyData once, ever - if not you will need to reset the flag and the CountdownLatch (which would then probably not be the most appropriate mechanism).

    EDIT

    Your latest comment indicates that you actually want to call loadSomeHeavyData more than once, just not more than once at a time.

    private final Semaphore updatePermit = new Semaphore(1);
    
    public void loadSomeHeavyData() {
        if (updatePermit.tryAcquire()) {
            //do the loading and release updatePermit when done
            updatePermit.release();
        } else {
            //update already running, wait
            updatePermit.acquire();
            //release the permit immediately
            updatePermit.release();
        }
    }
    
    0 讨论(0)
  • 2020-12-17 18:29
    public void someMethod()
    {  
        //DO SOME STUFF
        try
        {  
            doSomeProcessing();  
        }   
        catch (Exception e)
        {   
            loadSomeHeavyData();  // Don't call here but add a request to call in a queue.
                                  // OR update a counter
            doSomeProcessing();      
        }
    }
    

    One of the solutions could be to create a queue in which each thread puts its request to call loadSomeHeavyData. when no. of requests reach a threashold, block the execution of someMethod and call loadSomeHeavyData and clear the queue.

    Pseudocode may look like this:

    int noOfrequests = 0;
    public void someMethod()
    {
        // block incoming threads here.  
        while(isRefreshHappening);
    
        //DO SOME STUFF
        try
        { 
            doSomeProcessing();  
        }   
        catch (Exception e)
        {
            // Log the exception
            noOfrequests++;   
        }
    }
    
    // Will be run by only one thread
    public void RefreshData()
    {
        if(noOfrequests >= THRESHOLD)
        {
            isRefreshHappening = true;
            // WAIT if any thread is present in try block of somemethod
            // ...
            loadSomeHeavyData();
            noOfrequests = 0;
            isRefreshHappening = false;
        }
    }
    
    0 讨论(0)
  • 2020-12-17 18:34

    As I understand your question you need to load data in an unpredictable but limited time interval. There are three possible ways to do so: 1) You could surround your call to loadSomeHeavyData with an if statement which controls the access to the method. 2) You could change your method to handle the control flow (decision to update or not) 3) Write an update thread and let it do the work for you The first two alternatives could use an external boolean value or generate a boolean decision by using the timedelta between the last call und the current calling time. Third alternative would be a timed thread which runs every n seconds/minutes and load the heavy data.

    0 讨论(0)
提交回复
热议问题