Synchronise concurrent requests to share results of a slow operation
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
add a comment |
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
add a comment |
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
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
java concurrency countdownlatch
edited Nov 14 '18 at 12:45
jon-hanson
asked Nov 14 '18 at 11:58
jon-hansonjon-hanson
6,18122851
6,18122851
add a comment |
add a comment |
4 Answers
4
active
oldest
votes
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();
add a comment |
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.
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
|
show 1 more comment
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"
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
add a comment |
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
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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();
add a comment |
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();
add a comment |
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();
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();
edited Nov 15 '18 at 11:14
Aleksandr Semyannikov
591216
591216
answered Nov 15 '18 at 8:24
den'verden'ver
715
715
add a comment |
add a comment |
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.
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
|
show 1 more comment
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.
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
|
show 1 more comment
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.
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.
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
|
show 1 more comment
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
|
show 1 more comment
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"
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
add a comment |
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"
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
add a comment |
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"
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"
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
add a comment |
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
add a comment |
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
add a comment |
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
add a comment |
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
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
edited Nov 15 '18 at 7:25
answered Nov 14 '18 at 12:18
Aleksandr SemyannikovAleksandr Semyannikov
591216
591216
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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