Synchronise concurrent requests to share results of a slow operation










2















I have a Java UI service that has an API method that invokes an operation that's relatively slow (say ~30secs). The operation is parameterless, but it operates on external data that does change (relatively slowly) over time. It's not critical for the method to return the most up-to-date results - if they're 30secs old that's acceptable.



Ultimately I need to optimise the implementation of the slow operation, but as a short-term fix, I'd like to make the operation mutually exclusive, such that if a second incoming request (on a separate thread) attempts to invoke the operation while another is already in progress, then the second one blocks until the first one completes. The second thread then uses the results of the first invocation of the operation - i.e. it doesn't attempt to run the operation again.



E.g.:



class MyService 
String serviceApiMmethod()
// If a second thread attempts to call this method while another is in progress
// then block here until the first returns and then use those results
// (allowing it to return immediately without a second call to callSlowOperation).
return callSlowOperation();




What's the preferred general approach for this in Java (8). I'm guessing I could use a CountDownLatch, but it's not clear how best to share the result across the threads. Is there an existing concurrency primitive that facilitates this?



EDIT: I need to clear any references to the result once all threads have consumed it (i.e. returned it to the caller), as it's relatively large object, which needs to be GC'ed as soon as possible.










share|improve this question




























    2















    I have a Java UI service that has an API method that invokes an operation that's relatively slow (say ~30secs). The operation is parameterless, but it operates on external data that does change (relatively slowly) over time. It's not critical for the method to return the most up-to-date results - if they're 30secs old that's acceptable.



    Ultimately I need to optimise the implementation of the slow operation, but as a short-term fix, I'd like to make the operation mutually exclusive, such that if a second incoming request (on a separate thread) attempts to invoke the operation while another is already in progress, then the second one blocks until the first one completes. The second thread then uses the results of the first invocation of the operation - i.e. it doesn't attempt to run the operation again.



    E.g.:



    class MyService 
    String serviceApiMmethod()
    // If a second thread attempts to call this method while another is in progress
    // then block here until the first returns and then use those results
    // (allowing it to return immediately without a second call to callSlowOperation).
    return callSlowOperation();




    What's the preferred general approach for this in Java (8). I'm guessing I could use a CountDownLatch, but it's not clear how best to share the result across the threads. Is there an existing concurrency primitive that facilitates this?



    EDIT: I need to clear any references to the result once all threads have consumed it (i.e. returned it to the caller), as it's relatively large object, which needs to be GC'ed as soon as possible.










    share|improve this question


























      2












      2








      2


      1






      I have a Java UI service that has an API method that invokes an operation that's relatively slow (say ~30secs). The operation is parameterless, but it operates on external data that does change (relatively slowly) over time. It's not critical for the method to return the most up-to-date results - if they're 30secs old that's acceptable.



      Ultimately I need to optimise the implementation of the slow operation, but as a short-term fix, I'd like to make the operation mutually exclusive, such that if a second incoming request (on a separate thread) attempts to invoke the operation while another is already in progress, then the second one blocks until the first one completes. The second thread then uses the results of the first invocation of the operation - i.e. it doesn't attempt to run the operation again.



      E.g.:



      class MyService 
      String serviceApiMmethod()
      // If a second thread attempts to call this method while another is in progress
      // then block here until the first returns and then use those results
      // (allowing it to return immediately without a second call to callSlowOperation).
      return callSlowOperation();




      What's the preferred general approach for this in Java (8). I'm guessing I could use a CountDownLatch, but it's not clear how best to share the result across the threads. Is there an existing concurrency primitive that facilitates this?



      EDIT: I need to clear any references to the result once all threads have consumed it (i.e. returned it to the caller), as it's relatively large object, which needs to be GC'ed as soon as possible.










      share|improve this question
















      I have a Java UI service that has an API method that invokes an operation that's relatively slow (say ~30secs). The operation is parameterless, but it operates on external data that does change (relatively slowly) over time. It's not critical for the method to return the most up-to-date results - if they're 30secs old that's acceptable.



      Ultimately I need to optimise the implementation of the slow operation, but as a short-term fix, I'd like to make the operation mutually exclusive, such that if a second incoming request (on a separate thread) attempts to invoke the operation while another is already in progress, then the second one blocks until the first one completes. The second thread then uses the results of the first invocation of the operation - i.e. it doesn't attempt to run the operation again.



      E.g.:



      class MyService 
      String serviceApiMmethod()
      // If a second thread attempts to call this method while another is in progress
      // then block here until the first returns and then use those results
      // (allowing it to return immediately without a second call to callSlowOperation).
      return callSlowOperation();




      What's the preferred general approach for this in Java (8). I'm guessing I could use a CountDownLatch, but it's not clear how best to share the result across the threads. Is there an existing concurrency primitive that facilitates this?



      EDIT: I need to clear any references to the result once all threads have consumed it (i.e. returned it to the caller), as it's relatively large object, which needs to be GC'ed as soon as possible.







      java concurrency countdownlatch






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 14 '18 at 12:45







      jon-hanson

















      asked Nov 14 '18 at 11:58









      jon-hansonjon-hanson

      6,18122851




      6,18122851






















          4 Answers
          4






          active

          oldest

          votes


















          1














          Simple idea



          Version 1:



          class Foo 
          public String foo() throws Exception
          synchronized (this)
          if (counter.incrementAndGet() == 1)
          future = CompletableFuture.supplyAsync(() ->
          try
          Thread.sleep(1000 * (ThreadLocalRandom.current().nextInt(3) + 1));
          catch (InterruptedException e)

          return "ok" + ThreadLocalRandom.current().nextInt();
          );



          String result = future.get();
          if (counter.decrementAndGet() == 0)
          future = null;


          return result;


          private AtomicInteger counter = new AtomicInteger();
          private Future<String> future;



          Version 2: together with @AleksandrSemyannikov



          public class MyService 
          private AtomicInteger counter = new AtomicInteger();
          private volatile String result;

          public String serviceApiMethod()
          counter.incrementAndGet();
          try
          synchronized (this)
          if (result == null)
          result = callSlowOperation();


          return result;
          finally
          if (counter.decrementAndGet() == 0)
          synchronized (this)
          if (counter.get() == 0)
          result = null;






          private String callSlowOperation()
          try
          Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
          catch (InterruptedException e)
          e.printStackTrace();

          return Thread.currentThread().getName();







          share|improve this answer
































            0














            ReentrantReadWriteLock will be easier to use:



            class MyService 

            String result;
            ReadWriteLock lock = new ReentrantReadWriteLock();

            String serviceApiMmethod()
            lock.readLock().lock();
            try finally
            lock.readLock().unlock();





            Here, read lock protects against reading stale state, and write lock protects against performing "slow operation" by multiple threas simulateneously.






            share|improve this answer

























            • You check for result == null, but it's never set to null as far as i can see (other than initially).

              – jon-hanson
              Nov 14 '18 at 12:42












            • May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

              – Aleksandr Semyannikov
              Nov 14 '18 at 12:45












            • @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

              – Alex Salauyou
              Nov 14 '18 at 12:45











            • @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

              – Alex Salauyou
              Nov 14 '18 at 12:48






            • 1





              @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

              – Aleksandr Semyannikov
              Nov 14 '18 at 12:49


















            0














            Another approach (which I think might be even better) would be to add all threads that ask for result in a synchronized collection. Then when the result arrives - send the responses back to the threads. You can use the java 8 functional interface consumer to make it more fancy. It will not waste CPU time (like with thread.sleep or even with countDownLatch and other modern java concurrent classes). It would require these thread to have a callback method to accept the result but it might even make your code easier to read:



            class MyService 
            private static volatile boolean isProcessing;
            private synchronized static boolean isProcessing()
            return isProcessing;

            private static Set<Consumer<String>> callers=Collections.synchronizedSet(new HashSet<>());

            void serviceApiMmethod(Consumer<String> callBack)
            callers.add(callBack);
            callSlowOperation();


            private synchronized static void callSlowOperation()
            if(isProcessing())
            return;
            isProcessing=true;
            try Thread.sleep(1000); catch (Exception e) //Simulate slow operation
            final String result="slow result";
            callers.forEach(consumer-> consumer.accept(result));
            callers.clear();
            isProcessing=false;





            And the calling threads:



            class ConsumerThread implements Runnable
            final int threadNumber;
            public ConsumerThread(int num)
            this.threadNumber=num;


            public void processResponse(String response)
            System.out.println("Thread ["+threadNumber+"] got response:"+response+" at:"+System.currentTimeMillis());


            @Override
            public void run()
            new MyService().serviceApiMmethod(this::processResponse);






            This way the resulting object will be garbage collected because the all consumers will get it right away and release it.



            And to test the results:



            public class Test
            public static void main(String args)
            System.out.println(System.currentTimeMillis());
            for(int i=0;i<5;i++)
            new Thread(new ConsumerThread(i)).start();





            And the result:



            1542201686784
            Thread [1] got response:slow result at:1542201687827
            Thread [2] got response:slow result at:1542201687827
            Thread [3] got response:slow result at:1542201687827
            Thread [0] got response:slow result at:1542201687827
            Thread [4] got response:slow result at:1542201687827


            All threads got their result after 1 second. Kind of reactive programming ;) It does change the way into a more asynchronous way but if the caller of the thread wants to block the execution while getting the result he can achieve it. The service basically is self explanatory about what is done. It's like saying "my operation is slow so instead of running the call for each of you callers, I will send you the result once I am ready - give me a consumer method"






            share|improve this answer

























            • There is no guarantee that two threads won't read isProcessing = false simultaneously

              – Aleksandr Semyannikov
              Nov 14 '18 at 12:35











            • @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

              – jon-hanson
              Nov 14 '18 at 12:39












            • Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

              – Veselin Davidov
              Nov 14 '18 at 12:42











            • I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

              – Veselin Davidov
              Nov 14 '18 at 12:57


















            0














            As a solution you can use something like this:



            public class MyService 

            private volatile ResultHolder holder;

            public String serviceApiMethod()
            if (holder != null && !isTimedOut(holder.calculated))
            return holder.result;

            synchronized (this)
            if (holder != null && !isTimedOut(holder.calculated))
            return holder.result;

            String result = callSlowOperation();
            holder = new ResultHolder(result, LocalDateTime.now());
            return result;



            private static class ResultHolder
            private String result;
            private LocalDateTime calculated;

            public ResultHolder(String result, LocalDateTime calculated)
            this.result = result;
            this.calculated = calculated;





            Note that MyService must be singleton and ResultHolder must be immutable






            share|improve this answer
























              Your Answer






              StackExchange.ifUsing("editor", function ()
              StackExchange.using("externalEditor", function ()
              StackExchange.using("snippets", function ()
              StackExchange.snippets.init();
              );
              );
              , "code-snippets");

              StackExchange.ready(function()
              var channelOptions =
              tags: "".split(" "),
              id: "1"
              ;
              initTagRenderer("".split(" "), "".split(" "), channelOptions);

              StackExchange.using("externalEditor", function()
              // Have to fire editor after snippets, if snippets enabled
              if (StackExchange.settings.snippets.snippetsEnabled)
              StackExchange.using("snippets", function()
              createEditor();
              );

              else
              createEditor();

              );

              function createEditor()
              StackExchange.prepareEditor(
              heartbeatType: 'answer',
              autoActivateHeartbeat: false,
              convertImagesToLinks: true,
              noModals: true,
              showLowRepImageUploadWarning: true,
              reputationToPostImages: 10,
              bindNavPrevention: true,
              postfix: "",
              imageUploader:
              brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
              contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
              allowUrls: true
              ,
              onDemand: true,
              discardSelector: ".discard-answer"
              ,immediatelyShowMarkdownHelp:true
              );



              );













              draft saved

              draft discarded


















              StackExchange.ready(
              function ()
              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53299738%2fsynchronise-concurrent-requests-to-share-results-of-a-slow-operation%23new-answer', 'question_page');

              );

              Post as a guest















              Required, but never shown

























              4 Answers
              4






              active

              oldest

              votes








              4 Answers
              4






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              1














              Simple idea



              Version 1:



              class Foo 
              public String foo() throws Exception
              synchronized (this)
              if (counter.incrementAndGet() == 1)
              future = CompletableFuture.supplyAsync(() ->
              try
              Thread.sleep(1000 * (ThreadLocalRandom.current().nextInt(3) + 1));
              catch (InterruptedException e)

              return "ok" + ThreadLocalRandom.current().nextInt();
              );



              String result = future.get();
              if (counter.decrementAndGet() == 0)
              future = null;


              return result;


              private AtomicInteger counter = new AtomicInteger();
              private Future<String> future;



              Version 2: together with @AleksandrSemyannikov



              public class MyService 
              private AtomicInteger counter = new AtomicInteger();
              private volatile String result;

              public String serviceApiMethod()
              counter.incrementAndGet();
              try
              synchronized (this)
              if (result == null)
              result = callSlowOperation();


              return result;
              finally
              if (counter.decrementAndGet() == 0)
              synchronized (this)
              if (counter.get() == 0)
              result = null;






              private String callSlowOperation()
              try
              Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
              catch (InterruptedException e)
              e.printStackTrace();

              return Thread.currentThread().getName();







              share|improve this answer





























                1














                Simple idea



                Version 1:



                class Foo 
                public String foo() throws Exception
                synchronized (this)
                if (counter.incrementAndGet() == 1)
                future = CompletableFuture.supplyAsync(() ->
                try
                Thread.sleep(1000 * (ThreadLocalRandom.current().nextInt(3) + 1));
                catch (InterruptedException e)

                return "ok" + ThreadLocalRandom.current().nextInt();
                );



                String result = future.get();
                if (counter.decrementAndGet() == 0)
                future = null;


                return result;


                private AtomicInteger counter = new AtomicInteger();
                private Future<String> future;



                Version 2: together with @AleksandrSemyannikov



                public class MyService 
                private AtomicInteger counter = new AtomicInteger();
                private volatile String result;

                public String serviceApiMethod()
                counter.incrementAndGet();
                try
                synchronized (this)
                if (result == null)
                result = callSlowOperation();


                return result;
                finally
                if (counter.decrementAndGet() == 0)
                synchronized (this)
                if (counter.get() == 0)
                result = null;






                private String callSlowOperation()
                try
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
                catch (InterruptedException e)
                e.printStackTrace();

                return Thread.currentThread().getName();







                share|improve this answer



























                  1












                  1








                  1







                  Simple idea



                  Version 1:



                  class Foo 
                  public String foo() throws Exception
                  synchronized (this)
                  if (counter.incrementAndGet() == 1)
                  future = CompletableFuture.supplyAsync(() ->
                  try
                  Thread.sleep(1000 * (ThreadLocalRandom.current().nextInt(3) + 1));
                  catch (InterruptedException e)

                  return "ok" + ThreadLocalRandom.current().nextInt();
                  );



                  String result = future.get();
                  if (counter.decrementAndGet() == 0)
                  future = null;


                  return result;


                  private AtomicInteger counter = new AtomicInteger();
                  private Future<String> future;



                  Version 2: together with @AleksandrSemyannikov



                  public class MyService 
                  private AtomicInteger counter = new AtomicInteger();
                  private volatile String result;

                  public String serviceApiMethod()
                  counter.incrementAndGet();
                  try
                  synchronized (this)
                  if (result == null)
                  result = callSlowOperation();


                  return result;
                  finally
                  if (counter.decrementAndGet() == 0)
                  synchronized (this)
                  if (counter.get() == 0)
                  result = null;






                  private String callSlowOperation()
                  try
                  Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
                  catch (InterruptedException e)
                  e.printStackTrace();

                  return Thread.currentThread().getName();







                  share|improve this answer















                  Simple idea



                  Version 1:



                  class Foo 
                  public String foo() throws Exception
                  synchronized (this)
                  if (counter.incrementAndGet() == 1)
                  future = CompletableFuture.supplyAsync(() ->
                  try
                  Thread.sleep(1000 * (ThreadLocalRandom.current().nextInt(3) + 1));
                  catch (InterruptedException e)

                  return "ok" + ThreadLocalRandom.current().nextInt();
                  );



                  String result = future.get();
                  if (counter.decrementAndGet() == 0)
                  future = null;


                  return result;


                  private AtomicInteger counter = new AtomicInteger();
                  private Future<String> future;



                  Version 2: together with @AleksandrSemyannikov



                  public class MyService 
                  private AtomicInteger counter = new AtomicInteger();
                  private volatile String result;

                  public String serviceApiMethod()
                  counter.incrementAndGet();
                  try
                  synchronized (this)
                  if (result == null)
                  result = callSlowOperation();


                  return result;
                  finally
                  if (counter.decrementAndGet() == 0)
                  synchronized (this)
                  if (counter.get() == 0)
                  result = null;






                  private String callSlowOperation()
                  try
                  Thread.sleep(ThreadLocalRandom.current().nextInt(1000));
                  catch (InterruptedException e)
                  e.printStackTrace();

                  return Thread.currentThread().getName();








                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Nov 15 '18 at 11:14









                  Aleksandr Semyannikov

                  591216




                  591216










                  answered Nov 15 '18 at 8:24









                  den'verden'ver

                  715




                  715























                      0














                      ReentrantReadWriteLock will be easier to use:



                      class MyService 

                      String result;
                      ReadWriteLock lock = new ReentrantReadWriteLock();

                      String serviceApiMmethod()
                      lock.readLock().lock();
                      try finally
                      lock.readLock().unlock();





                      Here, read lock protects against reading stale state, and write lock protects against performing "slow operation" by multiple threas simulateneously.






                      share|improve this answer

























                      • You check for result == null, but it's never set to null as far as i can see (other than initially).

                        – jon-hanson
                        Nov 14 '18 at 12:42












                      • May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:45












                      • @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

                        – Alex Salauyou
                        Nov 14 '18 at 12:45











                      • @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

                        – Alex Salauyou
                        Nov 14 '18 at 12:48






                      • 1





                        @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:49















                      0














                      ReentrantReadWriteLock will be easier to use:



                      class MyService 

                      String result;
                      ReadWriteLock lock = new ReentrantReadWriteLock();

                      String serviceApiMmethod()
                      lock.readLock().lock();
                      try finally
                      lock.readLock().unlock();





                      Here, read lock protects against reading stale state, and write lock protects against performing "slow operation" by multiple threas simulateneously.






                      share|improve this answer

























                      • You check for result == null, but it's never set to null as far as i can see (other than initially).

                        – jon-hanson
                        Nov 14 '18 at 12:42












                      • May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:45












                      • @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

                        – Alex Salauyou
                        Nov 14 '18 at 12:45











                      • @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

                        – Alex Salauyou
                        Nov 14 '18 at 12:48






                      • 1





                        @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:49













                      0












                      0








                      0







                      ReentrantReadWriteLock will be easier to use:



                      class MyService 

                      String result;
                      ReadWriteLock lock = new ReentrantReadWriteLock();

                      String serviceApiMmethod()
                      lock.readLock().lock();
                      try finally
                      lock.readLock().unlock();





                      Here, read lock protects against reading stale state, and write lock protects against performing "slow operation" by multiple threas simulateneously.






                      share|improve this answer















                      ReentrantReadWriteLock will be easier to use:



                      class MyService 

                      String result;
                      ReadWriteLock lock = new ReentrantReadWriteLock();

                      String serviceApiMmethod()
                      lock.readLock().lock();
                      try finally
                      lock.readLock().unlock();





                      Here, read lock protects against reading stale state, and write lock protects against performing "slow operation" by multiple threas simulateneously.







                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited Nov 14 '18 at 12:50

























                      answered Nov 14 '18 at 12:31









                      Alex SalauyouAlex Salauyou

                      11.1k43360




                      11.1k43360












                      • You check for result == null, but it's never set to null as far as i can see (other than initially).

                        – jon-hanson
                        Nov 14 '18 at 12:42












                      • May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:45












                      • @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

                        – Alex Salauyou
                        Nov 14 '18 at 12:45











                      • @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

                        – Alex Salauyou
                        Nov 14 '18 at 12:48






                      • 1





                        @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:49

















                      • You check for result == null, but it's never set to null as far as i can see (other than initially).

                        – jon-hanson
                        Nov 14 '18 at 12:42












                      • May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:45












                      • @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

                        – Alex Salauyou
                        Nov 14 '18 at 12:45











                      • @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

                        – Alex Salauyou
                        Nov 14 '18 at 12:48






                      • 1





                        @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:49
















                      You check for result == null, but it's never set to null as far as i can see (other than initially).

                      – jon-hanson
                      Nov 14 '18 at 12:42






                      You check for result == null, but it's never set to null as far as i can see (other than initially).

                      – jon-hanson
                      Nov 14 '18 at 12:42














                      May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

                      – Aleksandr Semyannikov
                      Nov 14 '18 at 12:45






                      May be it's better to put lock.writeLock().lock(); inside if (result == null || staleResult()) cause you should guarantee that lock.writeLock will have unlocked. Also we need to know that result is not out of date

                      – Aleksandr Semyannikov
                      Nov 14 '18 at 12:45














                      @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

                      – Alex Salauyou
                      Nov 14 '18 at 12:45





                      @jon-hanson just use a queue where you put N copies of result (for number of threads), it will clear out when all items are consumed

                      – Alex Salauyou
                      Nov 14 '18 at 12:45













                      @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

                      – Alex Salauyou
                      Nov 14 '18 at 12:48





                      @AleksandrSemyannikov write lock is aquired only when there is need to peform the "slow operation", and is released right after it.

                      – Alex Salauyou
                      Nov 14 '18 at 12:48




                      1




                      1





                      @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

                      – Aleksandr Semyannikov
                      Nov 14 '18 at 12:49





                      @AlexSalauyou are you sure that it is impossible when lock.writeLock().lock() executed and result == null || staleResult() returned false?

                      – Aleksandr Semyannikov
                      Nov 14 '18 at 12:49











                      0














                      Another approach (which I think might be even better) would be to add all threads that ask for result in a synchronized collection. Then when the result arrives - send the responses back to the threads. You can use the java 8 functional interface consumer to make it more fancy. It will not waste CPU time (like with thread.sleep or even with countDownLatch and other modern java concurrent classes). It would require these thread to have a callback method to accept the result but it might even make your code easier to read:



                      class MyService 
                      private static volatile boolean isProcessing;
                      private synchronized static boolean isProcessing()
                      return isProcessing;

                      private static Set<Consumer<String>> callers=Collections.synchronizedSet(new HashSet<>());

                      void serviceApiMmethod(Consumer<String> callBack)
                      callers.add(callBack);
                      callSlowOperation();


                      private synchronized static void callSlowOperation()
                      if(isProcessing())
                      return;
                      isProcessing=true;
                      try Thread.sleep(1000); catch (Exception e) //Simulate slow operation
                      final String result="slow result";
                      callers.forEach(consumer-> consumer.accept(result));
                      callers.clear();
                      isProcessing=false;





                      And the calling threads:



                      class ConsumerThread implements Runnable
                      final int threadNumber;
                      public ConsumerThread(int num)
                      this.threadNumber=num;


                      public void processResponse(String response)
                      System.out.println("Thread ["+threadNumber+"] got response:"+response+" at:"+System.currentTimeMillis());


                      @Override
                      public void run()
                      new MyService().serviceApiMmethod(this::processResponse);






                      This way the resulting object will be garbage collected because the all consumers will get it right away and release it.



                      And to test the results:



                      public class Test
                      public static void main(String args)
                      System.out.println(System.currentTimeMillis());
                      for(int i=0;i<5;i++)
                      new Thread(new ConsumerThread(i)).start();





                      And the result:



                      1542201686784
                      Thread [1] got response:slow result at:1542201687827
                      Thread [2] got response:slow result at:1542201687827
                      Thread [3] got response:slow result at:1542201687827
                      Thread [0] got response:slow result at:1542201687827
                      Thread [4] got response:slow result at:1542201687827


                      All threads got their result after 1 second. Kind of reactive programming ;) It does change the way into a more asynchronous way but if the caller of the thread wants to block the execution while getting the result he can achieve it. The service basically is self explanatory about what is done. It's like saying "my operation is slow so instead of running the call for each of you callers, I will send you the result once I am ready - give me a consumer method"






                      share|improve this answer

























                      • There is no guarantee that two threads won't read isProcessing = false simultaneously

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:35











                      • @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

                        – jon-hanson
                        Nov 14 '18 at 12:39












                      • Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

                        – Veselin Davidov
                        Nov 14 '18 at 12:42











                      • I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

                        – Veselin Davidov
                        Nov 14 '18 at 12:57















                      0














                      Another approach (which I think might be even better) would be to add all threads that ask for result in a synchronized collection. Then when the result arrives - send the responses back to the threads. You can use the java 8 functional interface consumer to make it more fancy. It will not waste CPU time (like with thread.sleep or even with countDownLatch and other modern java concurrent classes). It would require these thread to have a callback method to accept the result but it might even make your code easier to read:



                      class MyService 
                      private static volatile boolean isProcessing;
                      private synchronized static boolean isProcessing()
                      return isProcessing;

                      private static Set<Consumer<String>> callers=Collections.synchronizedSet(new HashSet<>());

                      void serviceApiMmethod(Consumer<String> callBack)
                      callers.add(callBack);
                      callSlowOperation();


                      private synchronized static void callSlowOperation()
                      if(isProcessing())
                      return;
                      isProcessing=true;
                      try Thread.sleep(1000); catch (Exception e) //Simulate slow operation
                      final String result="slow result";
                      callers.forEach(consumer-> consumer.accept(result));
                      callers.clear();
                      isProcessing=false;





                      And the calling threads:



                      class ConsumerThread implements Runnable
                      final int threadNumber;
                      public ConsumerThread(int num)
                      this.threadNumber=num;


                      public void processResponse(String response)
                      System.out.println("Thread ["+threadNumber+"] got response:"+response+" at:"+System.currentTimeMillis());


                      @Override
                      public void run()
                      new MyService().serviceApiMmethod(this::processResponse);






                      This way the resulting object will be garbage collected because the all consumers will get it right away and release it.



                      And to test the results:



                      public class Test
                      public static void main(String args)
                      System.out.println(System.currentTimeMillis());
                      for(int i=0;i<5;i++)
                      new Thread(new ConsumerThread(i)).start();





                      And the result:



                      1542201686784
                      Thread [1] got response:slow result at:1542201687827
                      Thread [2] got response:slow result at:1542201687827
                      Thread [3] got response:slow result at:1542201687827
                      Thread [0] got response:slow result at:1542201687827
                      Thread [4] got response:slow result at:1542201687827


                      All threads got their result after 1 second. Kind of reactive programming ;) It does change the way into a more asynchronous way but if the caller of the thread wants to block the execution while getting the result he can achieve it. The service basically is self explanatory about what is done. It's like saying "my operation is slow so instead of running the call for each of you callers, I will send you the result once I am ready - give me a consumer method"






                      share|improve this answer

























                      • There is no guarantee that two threads won't read isProcessing = false simultaneously

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:35











                      • @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

                        – jon-hanson
                        Nov 14 '18 at 12:39












                      • Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

                        – Veselin Davidov
                        Nov 14 '18 at 12:42











                      • I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

                        – Veselin Davidov
                        Nov 14 '18 at 12:57













                      0












                      0








                      0







                      Another approach (which I think might be even better) would be to add all threads that ask for result in a synchronized collection. Then when the result arrives - send the responses back to the threads. You can use the java 8 functional interface consumer to make it more fancy. It will not waste CPU time (like with thread.sleep or even with countDownLatch and other modern java concurrent classes). It would require these thread to have a callback method to accept the result but it might even make your code easier to read:



                      class MyService 
                      private static volatile boolean isProcessing;
                      private synchronized static boolean isProcessing()
                      return isProcessing;

                      private static Set<Consumer<String>> callers=Collections.synchronizedSet(new HashSet<>());

                      void serviceApiMmethod(Consumer<String> callBack)
                      callers.add(callBack);
                      callSlowOperation();


                      private synchronized static void callSlowOperation()
                      if(isProcessing())
                      return;
                      isProcessing=true;
                      try Thread.sleep(1000); catch (Exception e) //Simulate slow operation
                      final String result="slow result";
                      callers.forEach(consumer-> consumer.accept(result));
                      callers.clear();
                      isProcessing=false;





                      And the calling threads:



                      class ConsumerThread implements Runnable
                      final int threadNumber;
                      public ConsumerThread(int num)
                      this.threadNumber=num;


                      public void processResponse(String response)
                      System.out.println("Thread ["+threadNumber+"] got response:"+response+" at:"+System.currentTimeMillis());


                      @Override
                      public void run()
                      new MyService().serviceApiMmethod(this::processResponse);






                      This way the resulting object will be garbage collected because the all consumers will get it right away and release it.



                      And to test the results:



                      public class Test
                      public static void main(String args)
                      System.out.println(System.currentTimeMillis());
                      for(int i=0;i<5;i++)
                      new Thread(new ConsumerThread(i)).start();





                      And the result:



                      1542201686784
                      Thread [1] got response:slow result at:1542201687827
                      Thread [2] got response:slow result at:1542201687827
                      Thread [3] got response:slow result at:1542201687827
                      Thread [0] got response:slow result at:1542201687827
                      Thread [4] got response:slow result at:1542201687827


                      All threads got their result after 1 second. Kind of reactive programming ;) It does change the way into a more asynchronous way but if the caller of the thread wants to block the execution while getting the result he can achieve it. The service basically is self explanatory about what is done. It's like saying "my operation is slow so instead of running the call for each of you callers, I will send you the result once I am ready - give me a consumer method"






                      share|improve this answer















                      Another approach (which I think might be even better) would be to add all threads that ask for result in a synchronized collection. Then when the result arrives - send the responses back to the threads. You can use the java 8 functional interface consumer to make it more fancy. It will not waste CPU time (like with thread.sleep or even with countDownLatch and other modern java concurrent classes). It would require these thread to have a callback method to accept the result but it might even make your code easier to read:



                      class MyService 
                      private static volatile boolean isProcessing;
                      private synchronized static boolean isProcessing()
                      return isProcessing;

                      private static Set<Consumer<String>> callers=Collections.synchronizedSet(new HashSet<>());

                      void serviceApiMmethod(Consumer<String> callBack)
                      callers.add(callBack);
                      callSlowOperation();


                      private synchronized static void callSlowOperation()
                      if(isProcessing())
                      return;
                      isProcessing=true;
                      try Thread.sleep(1000); catch (Exception e) //Simulate slow operation
                      final String result="slow result";
                      callers.forEach(consumer-> consumer.accept(result));
                      callers.clear();
                      isProcessing=false;





                      And the calling threads:



                      class ConsumerThread implements Runnable
                      final int threadNumber;
                      public ConsumerThread(int num)
                      this.threadNumber=num;


                      public void processResponse(String response)
                      System.out.println("Thread ["+threadNumber+"] got response:"+response+" at:"+System.currentTimeMillis());


                      @Override
                      public void run()
                      new MyService().serviceApiMmethod(this::processResponse);






                      This way the resulting object will be garbage collected because the all consumers will get it right away and release it.



                      And to test the results:



                      public class Test
                      public static void main(String args)
                      System.out.println(System.currentTimeMillis());
                      for(int i=0;i<5;i++)
                      new Thread(new ConsumerThread(i)).start();





                      And the result:



                      1542201686784
                      Thread [1] got response:slow result at:1542201687827
                      Thread [2] got response:slow result at:1542201687827
                      Thread [3] got response:slow result at:1542201687827
                      Thread [0] got response:slow result at:1542201687827
                      Thread [4] got response:slow result at:1542201687827


                      All threads got their result after 1 second. Kind of reactive programming ;) It does change the way into a more asynchronous way but if the caller of the thread wants to block the execution while getting the result he can achieve it. The service basically is self explanatory about what is done. It's like saying "my operation is slow so instead of running the call for each of you callers, I will send you the result once I am ready - give me a consumer method"







                      share|improve this answer














                      share|improve this answer



                      share|improve this answer








                      edited Nov 14 '18 at 14:35

























                      answered Nov 14 '18 at 12:19









                      Veselin DavidovVeselin Davidov

                      5,9351617




                      5,9351617












                      • There is no guarantee that two threads won't read isProcessing = false simultaneously

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:35











                      • @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

                        – jon-hanson
                        Nov 14 '18 at 12:39












                      • Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

                        – Veselin Davidov
                        Nov 14 '18 at 12:42











                      • I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

                        – Veselin Davidov
                        Nov 14 '18 at 12:57

















                      • There is no guarantee that two threads won't read isProcessing = false simultaneously

                        – Aleksandr Semyannikov
                        Nov 14 '18 at 12:35











                      • @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

                        – jon-hanson
                        Nov 14 '18 at 12:39












                      • Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

                        – Veselin Davidov
                        Nov 14 '18 at 12:42











                      • I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

                        – Veselin Davidov
                        Nov 14 '18 at 12:57
















                      There is no guarantee that two threads won't read isProcessing = false simultaneously

                      – Aleksandr Semyannikov
                      Nov 14 '18 at 12:35





                      There is no guarantee that two threads won't read isProcessing = false simultaneously

                      – Aleksandr Semyannikov
                      Nov 14 '18 at 12:35













                      @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

                      – jon-hanson
                      Nov 14 '18 at 12:39






                      @AleksandrSemyannikov Agreed, looks like a bug. Not keen on the sleep loop either.

                      – jon-hanson
                      Nov 14 '18 at 12:39














                      Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

                      – Veselin Davidov
                      Nov 14 '18 at 12:42





                      Yeah the access should go through synchronized getter I can agree to that. As for the sleep loop it might not be the greatest choice ;) I just wanted to show the idea

                      – Veselin Davidov
                      Nov 14 '18 at 12:42













                      I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

                      – Veselin Davidov
                      Nov 14 '18 at 12:57





                      I added another solution which I think might be better - using a consumer interface that allows thread to "subscribe" for the result instead of block and wait

                      – Veselin Davidov
                      Nov 14 '18 at 12:57











                      0














                      As a solution you can use something like this:



                      public class MyService 

                      private volatile ResultHolder holder;

                      public String serviceApiMethod()
                      if (holder != null && !isTimedOut(holder.calculated))
                      return holder.result;

                      synchronized (this)
                      if (holder != null && !isTimedOut(holder.calculated))
                      return holder.result;

                      String result = callSlowOperation();
                      holder = new ResultHolder(result, LocalDateTime.now());
                      return result;



                      private static class ResultHolder
                      private String result;
                      private LocalDateTime calculated;

                      public ResultHolder(String result, LocalDateTime calculated)
                      this.result = result;
                      this.calculated = calculated;





                      Note that MyService must be singleton and ResultHolder must be immutable






                      share|improve this answer





























                        0














                        As a solution you can use something like this:



                        public class MyService 

                        private volatile ResultHolder holder;

                        public String serviceApiMethod()
                        if (holder != null && !isTimedOut(holder.calculated))
                        return holder.result;

                        synchronized (this)
                        if (holder != null && !isTimedOut(holder.calculated))
                        return holder.result;

                        String result = callSlowOperation();
                        holder = new ResultHolder(result, LocalDateTime.now());
                        return result;



                        private static class ResultHolder
                        private String result;
                        private LocalDateTime calculated;

                        public ResultHolder(String result, LocalDateTime calculated)
                        this.result = result;
                        this.calculated = calculated;





                        Note that MyService must be singleton and ResultHolder must be immutable






                        share|improve this answer



























                          0












                          0








                          0







                          As a solution you can use something like this:



                          public class MyService 

                          private volatile ResultHolder holder;

                          public String serviceApiMethod()
                          if (holder != null && !isTimedOut(holder.calculated))
                          return holder.result;

                          synchronized (this)
                          if (holder != null && !isTimedOut(holder.calculated))
                          return holder.result;

                          String result = callSlowOperation();
                          holder = new ResultHolder(result, LocalDateTime.now());
                          return result;



                          private static class ResultHolder
                          private String result;
                          private LocalDateTime calculated;

                          public ResultHolder(String result, LocalDateTime calculated)
                          this.result = result;
                          this.calculated = calculated;





                          Note that MyService must be singleton and ResultHolder must be immutable






                          share|improve this answer















                          As a solution you can use something like this:



                          public class MyService 

                          private volatile ResultHolder holder;

                          public String serviceApiMethod()
                          if (holder != null && !isTimedOut(holder.calculated))
                          return holder.result;

                          synchronized (this)
                          if (holder != null && !isTimedOut(holder.calculated))
                          return holder.result;

                          String result = callSlowOperation();
                          holder = new ResultHolder(result, LocalDateTime.now());
                          return result;



                          private static class ResultHolder
                          private String result;
                          private LocalDateTime calculated;

                          public ResultHolder(String result, LocalDateTime calculated)
                          this.result = result;
                          this.calculated = calculated;





                          Note that MyService must be singleton and ResultHolder must be immutable







                          share|improve this answer














                          share|improve this answer



                          share|improve this answer








                          edited Nov 15 '18 at 7:25

























                          answered Nov 14 '18 at 12:18









                          Aleksandr SemyannikovAleksandr Semyannikov

                          591216




                          591216



























                              draft saved

                              draft discarded
















































                              Thanks for contributing an answer to Stack Overflow!


                              • Please be sure to answer the question. Provide details and share your research!

                              But avoid


                              • Asking for help, clarification, or responding to other answers.

                              • Making statements based on opinion; back them up with references or personal experience.

                              To learn more, see our tips on writing great answers.




                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function ()
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53299738%2fsynchronise-concurrent-requests-to-share-results-of-a-slow-operation%23new-answer', 'question_page');

                              );

                              Post as a guest















                              Required, but never shown





















































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown

































                              Required, but never shown














                              Required, but never shown












                              Required, but never shown







                              Required, but never shown







                              這個網誌中的熱門文章

                              Barbados

                              How to read a connectionString WITH PROVIDER in .NET Core?

                              Node.js Script on GitHub Pages or Amazon S3