How do I make it retry the send attempt if user data is null. Max 2 retries, 1 retry after 10 seconds?
public class UserHandler {
private List users = new ArrayList();
public void addUser(username) {} //adds user
public Userdata findUser(username) {} //finds user
public void sendTo(String username, String message) {
Userdata user = findUser(username);
if(user != null) {
Out out = new Out(user.getClientSocket());
out.println(message);
}
}
}
Do I really have to manually insert a thread and sleep it inside sendTo()?
EDIT: the server uses java 1.4.2
You're got more of an architectural problem to solve first. In a single-threaded program the sequence is normally:
Do stuff;
Call sendTo();
Do more stuff.
You have to work out if what you want is:
Do stuff;
Call sendTo();
If (2) fails, wait 10 seconds and sendTo() again;
If (3) fails, throw an error;
Do more stuff.
The point being that this is still synchronous. If so you'll need a thread. You should use the Java 5 Executors.
public void sendTo(final String username, final String message) {
if (!internalSendTo(username, message)) {
// attempt resend
ExecutorService exec = Executors.newSingleThreadExecutor();
final AtomicBoolean result = new AtomicBoolean(false);
exec.submit(new Runnable() {
boolean b = internalSendto(username, message);
result.set(b);
});
try {
exec.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// still didn't work
} finally {
exec.shutdownNow();
}
}
}
private boolean internalSendTo(String username, String message) {
Userdata user = findUser(username);
boolean success = false;
if (user != null) {
Out out = new Out(user.getClientSocket());
// do the communication here
success = true;
}
return success;
}
Now that's just a rough sketch of how it might work. It should give you some appreciation for the issues however.
Do you want this or do you want:
Do stuff;
Call sendTo();
If (2) fails, queue the send and keep going;
Do more stuff.
Basically this is the asynchronous approach. If you go this way you then have to answer questions like:
What happens if after 10+ seconds (or some arbitrary interval) it still hasn't worked?
What processes attempt the sendTo() calls?
What if they block/die?
Do I need multiple senders?
etc
Basically it gets much more complicated.
I would recommend to use AOP and Java annotations. Try a read-made mechanism from jcabi-aspects:
#RetryOnFailure(attempts = 3)
public void sendTo(String username, String message) {
// try to do it
}
Related
I am just learning multithreading in Spring Framework and I don't know how to deal with one case. I have a long-lasting operation and I do not want the user to wait for it to be done, I find out that there is an #Async annotation which flag the method as executable asynchronously.
My question is what would be the best way to block this method such that users from the same company cannot perform it on the same time. Being accurate I want to block even performing analyzeData(...) and anlyzeStatistics(...) by users from the same company on the same time.
I was thinking about using ConcurrentHashMap with user company as key and boolean as value and checking it before performing the operation. I wonder if I'm going in the right direction, or maybe there are other more appropriate options offered by Spring.
#Service
public class LongOperationService {
#Async
public void analyzeData(User user, List<String> data) {
boolean operationResult = performLongOperation(data);
if (opeartionResult) {
log.info("Long operation ended successfully")
} else {
log.error("Long operation failure")
}
}
#Async
public void analyzeStatistics(User user, List<String> statistics) {
...
}
private void performLongOperation(List<String> data) {
// Just for demonstration
Thread.sleep(10000);
return true;
}
}
public class User {
String username;
String company;
}
You can use Semaphore to limit number of threads accessing a resource.
Since you want to prevent users from same company to access your analyze function concurrently, you should create semaphore per company:
// Init on startup
// Key should be a unique identifier to a company, I assume the `String company` as key, you should adjust as your real requirement
static final Map<String, Semaphore> COMPANY_ENTRANT = new ConcurrentHashMap<>();
// for each company
COMPANY_ENTRANT.put(companyId, new Semaphore(1));
Now in your service:
#Async
public void analyzeData(User user, List<String> data) {
Semaphore entrant = COMPANY_ENTRANT.get(user.getCompany());
try {
entrant.acquire();
try {
boolean operationResult = performLongOperation(data);
if (opeartionResult) {
log.info("Long operation ended successfully")
} else {
log.error("Long operation failure")
}
} finally {
entrant.release();
}
} catch(InterruptedException e) {
...
}
}
If you want a lazy initialization of the COMPANY_ENTRANT map, you can use putIfAbsent:
Semaphore entrant = COMPANY_ENTRANT.putIfAbsent(user.getCompany(), new Semaphore(1));
Try something like this:
private final Set<String> runningOperations = Collections.synchronizedSet(new HashSet<>());
private final Object lock = new Object();
#Async
public void analyzeData(User user, List<String> data) throws Exception {
synchronized (lock) {
if (runningOperations.contains(user.company))
return;
runningOperations.add(user.company);
}
try {
boolean operationResult = performLongOperation(data);
if (operationResult) {
log.info("Long operation ended successfully");
} else {
log.error("Long operation failure");
}
} finally {
runningOperations.remove(user.company);
}
}
I am using below class to send data to our messaging queue by using socket either in a synchronous way or asynchronous way as shown below.
sendAsync - It sends data asynchronously without any timeout. After sending (on LINE A) it adds to retryHolder bucket so that if acknowledgement is not received then it will retry again from the background thread which is started in a constructor.
send - It internally calls sendAsync method and then sleep for a particular timeout period and if acknowledgement is not received then it removes from retryHolder bucket so that we don't retry again.
So the only difference between those two above methods is - For async I need to retry at all cost but for sync I don't need to retry but looks like it might be getting retried since we share the same retry bucket cache and retry thread runs every 1 second.
ResponsePoller is a class which receives the acknowledgement for the data that was sent to our messaging queue and then calls removeFromretryHolder method below to remove the address so that we don't retry after receiving the acknowledgement.
public class SendToQueue {
private final ExecutorService cleanupExecutor = Executors.newFixedThreadPool(5);
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
private final Cache<Long, byte[]> retryHolder =
CacheBuilder
.newBuilder()
.maximumSize(1000000)
.concurrencyLevel(100)
.removalListener(
RemovalListeners.asynchronous(new LoggingRemovalListener(), cleanupExecutor)).build();
private static class Holder {
private static final SendToQueue INSTANCE = new SendToQueue();
}
public static SendToQueue getInstance() {
return Holder.INSTANCE;
}
private SendToQueue() {
executorService.submit(new ResponsePoller()); // another thread which receives acknowledgement and then delete entry from the `retryHolder` cache accordingly.
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
// retry again
for (Entry<Long, byte[]> entry : retryHolder.asMap().entrySet()) {
sendAsync(entry.getKey(), entry.getValue());
}
}
}, 0, 1, TimeUnit.SECONDS);
}
public boolean sendAsync(final long address, final byte[] encodedRecords, final Socket socket) {
ZMsg msg = new ZMsg();
msg.add(encodedRecords);
// send data on a socket LINE A
boolean sent = msg.send(socket);
msg.destroy();
retryHolder.put(address, encodedRecords);
return sent;
}
public boolean send(final long address, final byte[] encodedRecords, final Socket socket) {
boolean sent = sendAsync(address, encodedRecords, socket);
// if the record was sent successfully, then only sleep for timeout period
if (sent) {
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
// if key is not present, then acknowledgement was received successfully
sent = !retryHolder.asMap().containsKey(address);
// and key is still present in the cache, then it means acknowledgment was not received after
// waiting for timeout period, so we will remove it from cache.
if (!sent)
removeFromretryHolder(address);
return sent;
}
public void removeFromretryHolder(final long address) {
retryHolder.invalidate(address);
}
}
What is the best way by which we dont retry if anyone is calling send method but we still need to know whether acknowledgement was received or not. Only thing is I dont need to retry at all.
Do we need separate bucket for all the sync calls just for acknowledgement and we dont retry from that bucket?
The code has a number of potential issues:
An answer may be received before the call to retryHolder#put.
Possibly there is a race condition when messages are retried too.
If two messages are sent to the same address the second overwrites the first?
Send always wastes time with a sleep, use a wait+notify instead.
I would store a class with more state instead. It could contain a flag (retryIfNoAnswer yes/no) that the retry handler could check. It could provide waitForAnswer/markAnswerReceived methods using wait/notify so that send doesn't have to sleep for a fixed time. The waitForAnswer method can return true if an answer was obtained and false on timeout. Put the object in the retry handler before sending and use a timestamp so that only messages older than a certain age are retried. That fixes the first race condition.
EDIT: updated example code below, compiles with your code, not tested:
public class SendToQueue {
private final ExecutorService cleanupExecutor = Executors.newFixedThreadPool(5);
private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
// Not sure why you are using a cache rather than a standard ConcurrentHashMap?
private final Cache<Long, PendingMessage> cache = CacheBuilder.newBuilder().maximumSize(1000000)
.concurrencyLevel(100)
.removalListener(RemovalListeners.asynchronous(new LoggingRemovalListener(), cleanupExecutor)).build();
private static class PendingMessage {
private final long _address;
private final byte[] _encodedRecords;
private final Socket _socket;
private final boolean _retryEnabled;
private final Object _monitor = new Object();
private long _sendTimeMillis;
private volatile boolean _acknowledged;
public PendingMessage(long address, byte[] encodedRecords, Socket socket, boolean retryEnabled) {
_address = address;
_sendTimeMillis = System.currentTimeMillis();
_encodedRecords = encodedRecords;
_socket = socket;
_retryEnabled = retryEnabled;
}
public synchronized boolean hasExpired() {
return System.currentTimeMillis() - _sendTimeMillis > 500L;
}
public synchronized void markResent() {
_sendTimeMillis = System.currentTimeMillis();
}
public boolean shouldRetry() {
return _retryEnabled && !_acknowledged;
}
public boolean waitForAck() {
try {
synchronized(_monitor) {
_monitor.wait(500L);
}
return _acknowledged;
}
catch (InterruptedException e) {
return false;
}
}
public void ackReceived() {
_acknowledged = true;
synchronized(_monitor) {
_monitor.notifyAll();
}
}
public long getAddress() {
return _address;
}
public byte[] getEncodedRecords() {
return _encodedRecords;
}
public Socket getSocket() {
return _socket;
}
}
private static class Holder {
private static final SendToQueue INSTANCE = new SendToQueue();
}
public static SendToQueue getInstance() {
return Holder.INSTANCE;
}
private void handleRetries() {
List<PendingMessage> messages = new ArrayList<>(cache.asMap().values());
for (PendingMessage m : messages) {
if (m.hasExpired()) {
if (m.shouldRetry()) {
m.markResent();
doSendAsync(m, m.getSocket());
}
else {
// Or leave the message and let send remove it
cache.invalidate(m.getAddress());
}
}
}
}
private SendToQueue() {
executorService.submit(new ResponsePoller()); // another thread which receives acknowledgement and then delete entry from the cache accordingly.
executorService.scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
handleRetries();
}
}, 0, 1, TimeUnit.SECONDS);
}
public boolean sendAsync(final long address, final byte[] encodedRecords, final Socket socket) {
PendingMessage m = new PendingMessage(address, encodedRecords, socket, true);
cache.put(address, m);
return doSendAsync(m, socket);
}
private boolean doSendAsync(final PendingMessage pendingMessage, final Socket socket) {
ZMsg msg = new ZMsg();
msg.add(pendingMessage.getEncodedRecords());
try {
// send data on a socket LINE A
return msg.send(socket);
}
finally {
msg.destroy();
}
}
public boolean send(final long address, final byte[] encodedRecords, final Socket socket) {
PendingMessage m = new PendingMessage(address, encodedRecords, socket, false);
cache.put(address, m);
try {
if (doSendAsync(m, socket)) {
return m.waitForAck();
}
return false;
}
finally {
// Alternatively (checks that address points to m):
// cache.asMap().remove(address, m);
cache.invalidate(address);
}
}
public void handleAckReceived(final long address) {
PendingMessage m = cache.getIfPresent(address);
if (m != null) {
m.ackReceived();
cache.invalidate(address);
}
}
}
And called from ResponsePoller:
SendToQueue.getInstance().handleAckReceived(addressFrom);
Design-wise: I feel like you are trying to write a thread-safe and somewhat efficient NIO message sender/receiver but (both) code I see here aren't OK and won't be without significant changes. The best thing to do is either:
make full use of the 0MQ framework. I see things and expectations here that are actually available out-of-the-box in ZMQ and java.util.concurrent API.
or have a look at Netty (https://netty.io/index.html) preferably if it applies to your project. "Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients." This will save you time if your project gets complex, otherwise it might be overkill to start with (but then expect issues ...).
However if you think you are almost at it with your code or #john's code then I will just give advices to complete:
don't use wait() and notify(). Don't sleep() either.
use a single thread for your "flow tracker" (i.e. ~the pending message Cache).
You don't actually need 3 threads to process pending messages except if this processing itself is slow (or does heavy stuff) which is not the case here as you basically make an async call (as far as it is really async.. is it?).
The same for the reverse path: use an executor service (multiple threads) for your received packets processing only if the actual processing is slow/blocking or heavy.
I'm not an expert in 0MQ at all but as far as socket.send(...) is thread-safe and non-blocking (which I'm not sure personally - tell me) the above advices shall be correct and make things simpler.
That said, to strictly answer your question:
Do we need separate bucket for all the sync calls just for acknowledgement and we dont retry from that bucket?
I'd say no, hence what do you think of the following? Based on your code and independently of my own feelings this seems acceptable:
public class SendToQueue {
// ...
private final Map<Long, Boolean> transactions = new ConcurrentHashMap<>();
// ...
private void startTransaction(long address) {
this.transactions.put(address, Boolean.FALSE);
}
public void updateTransaction(long address) {
Boolean state = this.transactions.get(address);
if (state != null) {
this.transactions.put(address, Boolean.TRUE);
}
}
private void clearTransaction(long address) {
this.transactions.remove(address);
}
public boolean send(final long address, final byte[] encodedRecords, final Socket socket) {
boolean success = false;
// If address is enough randomized or atomically counted (then ok for parallel send())
startTransaction(address);
try {
boolean sent = sendAsync(address, encodedRecords, socket);
// if the record was sent successfully, then only sleep for timeout period
if (sent) {
// wait for acknowledgement
success = waitDoneUntil(new DoneCondition() {
#Override
public boolean isDone() {
return SendToQueue.this.transactions.get(address); // no NPE
}
}, 500, TimeUnit.MILLISECONDS);
if (success) {
// Message acknowledged!
}
}
} finally {
clearTransaction(address);
}
return success;
}
public static interface DoneCondition {
public boolean isDone();
}
/**
* WaitDoneUntil(Future f, int duration, TimeUnit unit). Note: includes a
* sleep(50).
*
* #param f Will block for this future done until maxWaitMillis
* #param waitTime Duration expressed in (time) unit.
* #param unit Time unit.
* #return DoneCondition finally met or not
*/
public static boolean waitDoneUntil(DoneCondition f, int waitTime, TimeUnit unit) {
long curMillis = 0;
long maxWaitMillis = unit.toMillis(waitTime);
while (!f.isDone() && curMillis < maxWaitMillis) {
try {
Thread.sleep(50); // define your step here accordingly or set as parameter
} catch (InterruptedException ex1) {
//logger.debug("waitDoneUntil() interrupted.");
break;
}
curMillis += 50L;
}
return f.isDone();
}
//...
}
public class ResponsePoller {
//...
public void onReceive(long address) { // sample prototype
// ...
SendToQueue.getInstance().updateTransaction(address);
// The interested sender will know that its transaction is complete.
// While subsequent (late) calls will have no effect.
}
}
Can I get a future object from a handler?
Handler handler = new Handler(getMainLooper());
Future<String> future = handler.post(new Callable<String>() {
public String call() throw Exception {
// run in the main thread
return askForPassword();
}
}); // can I do something like this?
String password = future.get(); // wait until finish
// do network things...
I have a network thread and I need to ask the user for password, since I need to show an input dialog I have to do this on Main thread, but handler can not return values.
I can do the same thing by
Handler handler = new Handler(getMainLooper());
String password = null;
handler.post(() -> {
// run in the main thread
password = askForPassword();
});
while (password == null) { /*wait until finish*/ }
// do network things...
But this looks stupid and inconvenient
Handler is fundamentally asynchronous and thus whatever you put there is not guaranteed to run immediately (moreover, you can postDelayed or postAtTime). Then it is clear, that you can not return any value from the Handler directly to the code that posted it. So you have to work this around.
Another obstacle is that in Java your closure can capture only final variables and you have to work this around as well.
New API (CompletableFuture)
Unfortunately original Future Java API is not suitable for composition. If you target new devices only and thus can use newer CompletableFuture, you may do something like this:
CompletableFuture<String> askForPasswordNewApi() {
// your UI code that creates Future
}
void doNetworkThingNewApi() {
// some network stuff
final CompletableFuture<String> passwordFutureWrapper = new CompletableFuture<String>();
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
// run in the main thread
CompletableFuture<String> future = askForPasswordNewApi();
// bind the real future to the outer one
future.handle((r, ex) -> {
if (ex != null)
passwordFutureWrapper.completeExceptionally(ex);
else
passwordFutureWrapper.complete(r);
return 0;
});
}
});
// wait until finish
// don't forget to handle InterruptedException here
String password = passwordFutureWrapper.get();
// do more network things...
}
The idea is rather simple: create outer final variable passwordFutureWrapper that can be captured by the Handler and bind this wrapper to the real future
Side note: if your askForPassword already returns Future but you can't use the new API, you probably have re-implemented something similar to CompletableFuture anyway, so you just need to modify this code a bit to allow binding of one future to another.
Old API
If you can't use CompletableFuture in your code yet, but still somehow has a method with a Future-based signature:
Future<String> askForPasswordOldApi()
you may do it more explicitly:
void doNetworkThingOldApi() {
// some network stuff
final CountDownLatch syncLock = new CountDownLatch(1);
final Future<String>[] futureWrapper = new Future<String>[1];
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
// run in the main thread
final CompletableFuture<String> future = askForPasswordOldApi();
futureWrapper[0] = future;
syncLock.countDown();
}
});
String password;
try {
// 1 minute should be quite enough for synchronization between threads
if (!syncLock.await(1, TimeUnit.MINUTES)) {
// log error, show some user feedback and then stop further processing
return;
}
password = futureWrapper[0].get(); // wait until finish
} catch (InterruptedException ex) {
// log error, show some user feedback and then stop further processing
return;
} catch (ExecutionException ex) {
// log error, show some user feedback and then stop further processing
return;
}
// do more network things...
}
The idea here is following:
Use single-element array as a simple container to work around final-closure limitations
Use CountDownLatch to ensure synchronization between the network and the UI threads i.e. that futureWrapper[0] is not null by the time we start waiting on the result with get.
Update (design for library API)
If you are designing API and want to have a single entry for login with different additional scenarios handled by a callback, I'd do using custom implementation of something similar to CompletableFuture:
public interface ResultHandler<T> {
void resolve(T result);
void cancel();
}
class ResultHandlerImpl<T> implements ResultHandler<T> {
enum State {
Empty,
Resolved,
Cancelled
}
private final Object _lock = new Object();
private State _state = State.Empty;
private T _result;
#Override
public void resolve(T result) {
synchronized (_lock) {
if (_state != State.Empty) // don't override current state
return;
_result = result;
_state = State.Resolved;
_lock.notifyAll();
}
}
#Override
public void cancel() {
synchronized (_lock) {
if (_state != State.Empty) // don't override current state
return;
_state = State.Cancelled;
_lock.notifyAll();
}
}
public boolean isCancelled() {
synchronized (_lock) {
return _state == State.Cancelled;
}
}
public boolean isDone() {
synchronized (_lock) {
return _state == State.Resolved;
}
}
public T get() throws InterruptedException, CancellationException {
while (_state == State.Empty) {
synchronized (_lock) {
_lock.wait();
}
}
if (_state == State.Resolved)
return _result;
else
throw new CancellationException();
}
}
I would probably make interface public but implementation ResultHandlerImpl package-private so it would be harder for the users to mess up with implementation details. Then in callback methods I'd pass my callback ResultHandler as a parameter (actually it obviously would be a ResultHandlerImpl):
public interface LoginCallback {
void askForPassword(ResultHandler<String> resultHandler);
}
And my login method would look something like this (assuming you have private methods tryRestoreSession that doesn't need password and loginWithPassword that requires):
public boolean login(final LoginCallback loginCallback) {
if (tryRestoreSession()) {
return true;
} else {
final ResultHandlerImpl<String> passwordHandler = new ResultHandlerImpl<>();
Handler handler = new Handler(getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
// run in the main thread
loginCallback.askForPassword(passwordHandler);
}
});
String password;
try {
password = passwordHandler.get();
} catch (CancellationException e) {
return false;
} catch (InterruptedException e) {
return false;
}
return loginWithPassword(password);
}
}
What I think is important here:
I think that passing a callback to LoginCallback makes it easier to write an asynchronous UI-based implementation using standard Java.
ResultHandlerImpl has cancel method. So if the user, for example, forgot the password there is a way to cancel whole login process and not get stuck with a background thread waiting for a password forever
ResultHandlerImpl uses explicit synchronization and wait/notifyAll to establish happens-before relationship between actions on different threads to avoid.
ResultHandlerImpl uses wait/notifyAll so the background thread doesn't consume CPU (and battery) while waiting for the UI.
UPDATED using "wait and notify" instead of looping
UPDATED 2 synchronized methods
Finally I end up with object wrapper(thansk to #SergGr ) and handler
class ObjectWrapper<T> {
T object;
boolean ready;
synchronized void set(T object) {
this.object = object;
this.ready = true;
notifyAll();
}
T get() {
while (!ready) {
synchronized(this) {
try {
wait();
} catch (InterruptedException e) {
return null;
}
}
}
return object;
}
}
In my network thread
Handler handler = new Handler(getMainLooper());
ObjectWarpper<String> wrapper = new ObjectWarpper<>();
handler.post(() -> wrapper.set(askForPassword()));
String password = wrapper.get();
I'm doing an exercise about Java concurrency using wait, notify to study for an exam.
The exam will be written, so the code does have to be perfect since we can't try to compile and check errors.
This is the text of the exercise:
General idea:
when the downloader is instanced the queue and the hashmap are created and passed to all the threads. (shared data)
the download method add the url to the queue and call notifyAll to wake up the Downloader Threads.
the getData method waits until there are data in the hashmap for the provided url. When data are available it returns to the caller.
the downloader thread runs an infinity loop. It waits until an url is present in the queue. When it receives an url it downloads it and puts the bytes in the hashmap calling notifyAll to wake up a possible user waiting in getData method.
This is the code that I produced:
public class Downloader{
private Queue downloadQueue;
private HashMap urlData;
private final static THREADS_NUMBER;
public Downloader(){
this.downloadQueue = new Queue();
this.urlData = new HashMap();
for(int i = 0; i < THREADS_NUMBER; i++){
new DownTh(this.downloadQueue, this.urlData).start();
}
}
void syncronized download(String URL){
downloadQueue.add(url);
notifyAll();
return;
}
byte[] syncronized getData(String URL){
while(urlData.get(URL) == null ){
wait()
}
return urlData.get(URL);
}
}
public class DownTh extend Thread{
private Queue downloadQueue;
private HashMap urlData;
public DownTh(Queue downloadQueue, HashMap urlData){
this.downloadQueue = downloadQueue
this.urlData = urlData;
}
public void run(){
while(true){
syncronized{
while(queue.isEmpty()){
wait()
}
String url = queue.remove();
urlData.add(url, Util.download(url))
notifyAll()
}
}
}
}
Can you help me and tell me if the logic is right?
Let's assume for a second that all those great classes in Java that handle synchronization do not exist, because this is a synthetic task, and all you got to handle is sychronized, wait and notify.
The first question to answer in simple words is: "Who is going to wait on what?"
The download thread is going to wait for an URL to download.
The caller is going to wait for the result of that download thread.
What does this mean in detail? We need at least one synchronization element between the caller and the download thread (your urlData), also there should be one data object handling the download data itself for convenience, and to check whether or not the download has yet been completed.
So the detailed steps that will happen are:
Caller requests new download.create: DownloadResultwrite: urlData(url -> DownloadResult)wake up 1 thread on urlData.
Thread X must find data to download and process it or/then fall asleep again.read: urlData (find first unprocessed DownloadResult, otherwise wait on urlData)write: DownloadResult (acquire it)write: DownloadResult (download result)notify: anyone waiting on DownloadResultrepeat
Caller must be able to asynchronously check/wait for download result.read: urlDataread: DownloadResult (wait on DownloadResult if required)
As there are reads and writes from different threads on those objects, synchronization is required when accessing the objects urlData or DownloadResult.
Also there will be a wait/notify association:
caller -> urlData -> DownTh
DownTh -> DownloadResult -> caller
After careful analysis the following code would fulfill the requirements:
public class DownloadResult {
protected final URL url; // this is for convenience
protected boolean inProgress;
protected byte[] result;
public DownloadResult(final URL url) {
this.url = url;
this.inProgress = false;
}
/* Try to lock this against tother threads if not already acquired. */
public synchronized boolean acquire() {
if (this.inProgress == false) {
this.inProgress = true;
return true;
} else {
return false;
}
}
public void download() {
final byte[] downloadedBytes = Util.download(this.url); // note how this is done outside the synchronized block to avoid unnecessarily long blockings
synchronized (this) {
this.result = downloadedBytes;
this.notifyAll(); // wake-up ALL callers
}
}
public synchronized byte[] getResult() throws InterruptedException {
while (this.result == null) {
this.wait();
}
return this.result;
}
}
protected class DownTh extends Thread {
protected final Map<URL, DownloadResult> urlData;
public DownTh(final Map<URL, DownloadResult> urlData) {
this.urlData = urlData;
this.setDaemon(true); // this allows the JVM to shut down despite DownTh threads still running
}
protected DownloadResult getTask() {
for (final DownloadResult downloadResult : urlData.values()) {
if (downloadResult.acquire()) {
return downloadResult;
}
}
return null;
}
#Override
public void run() {
DownloadResult downloadResult;
try {
while (true) {
synchronized (urlData) {
while ((downloadResult = this.getTask()) == null) {
urlData.wait();
}
}
downloadResult.download();
}
} catch (InterruptedException ex) {
// can be ignored
} catch (Error e) {
// log here
}
}
}
public class Downloader {
protected final Map<URL, DownloadResult> urlData = new HashMap<>();
// insert constructor that creates the threads here
public DownloadResult download(final URL url) {
final DownloadResult result = new DownloadResult(url);
synchronized (urlData) {
urlData.putIfAbsent(url, result);
urlData.notify(); // only one thread needs to wake up
}
return result;
}
public byte[] getData(final URL url) throws InterruptedException {
DownloadResult result;
synchronized (urlData) {
result = urlData.get(url);
}
if (result != null) {
return result.getResult();
} else {
throw new IllegalStateException("URL " + url + " not requested.");
}
}
}
In real Java things would be done differently, by using Concurrent classes and/or Atomic... classes, so this is just for educational purposes. For further reading see "Callable Future".
I have an external API I have to call that requires a validation token. The app calling the API will be threaded. Also, only 5 concurrent connections are allowed. I'm going to use a fixed thread pool for the connections, but I'm having an issue with figuring out how to handle an expired/invalid token. What I want to do is, when one thread encounters an expired token, prevent the other threads from acquiring the token until it has been refreshed. I'm thinking of using ReentrantLock to do this, but I'm not sure if my implementation is correct.
public static void main(String[] args){
for(int i = 0; i < 100; i++){
new Thread(new LockTest()).start();
}
}
public void testLock(String message) throws InterruptedException{
try{
getToken(message);
/*
* Use token here
*/
Thread.sleep(1000);
Random r = new Random();
int num = r.nextInt((25-0) + 1);
if(num == 1){ //testing only - exception thrown randomly.
throw new Exception("Token Expired!");
}
System.out.println("Message: " + message);
}catch(Exception e){
System.err.println(e.getMessage());
awaitTokenRefresh = true;
refreshToken();
}
}
private void refreshToken() throws InterruptedException {
lock.lock();
try{
System.out.println("Refreshing token...");
Thread.sleep(2000l);
System.out.println("Refreshed!");
awaitTokenRefresh = false;
awaitRefresh.signalAll();
}
finally{
lock.unlock();
}
}
//test use case for salesforce token
private void getToken(String message) throws InterruptedException {
lock.lock();
try{
while(awaitTokenRefresh){
System.out.println(message + " waiting for token refresh...");
awaitRefresh.await();
}
}
finally{
lock.unlock();
}
}
public void run(){
try {
System.out.println("Starting thread...");
testLock(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Just for testing, I've put in some sleeps to mimic work being done. The main thing I don't know about is, when thread A unlocks inside of getToken() thread B enters, but we don't know if the token is invalid yet. So B could actually be getting a bad token that A has to find. Is there a good way to handle this? Or is the idea of using locks completely wrong?
The first thing I notice is that your code is not properly synchronized. The exception handler in testLock() modifies shared variable awaitTokenRefresh at a point where that write is not ordered relative to other threads reading its value in getToken().
The main thing I don't know about is, when thread A unlocks inside of getToken() thread B enters, but we don't know if the token is invalid yet. So B could actually be getting a bad token that A has to find. Is there a good way to handle this? Or is the idea of using locks completely wrong?
I guess what you really want to avoid is unnecessary token refreshes when the current token becomes invalid. Exactly one thread should refresh it; the others should simply wait for the refresh and then continue about their business. The problem with your approach is that the threads have no good way to determine whether they are the first to detect the expiration, and so should take responsibility for refreshing. And indeed that makes sense, because the concept of which thread does anything first is not always well defined in a multithreaded application.
Whether you use locks vs. synchronization is an implementation detail of relatively minor consequence. The key is that you must have some shared state that tells threads whether the token they propose to refresh is in fact still current at all. I might implement it something like this:
public class MyClass {
private Object token = null;
private final Object tokenMonitor = new Object();
// ...
private Object getToken() {
synchronized (tokenMonitor) {
if (token == null) {
return refreshToken(null);
} else {
return token;
}
}
}
private Object refreshToken(Object oldToken) {
synchronized (tokenMonitor) {
if (token == oldToken) { // test reference equality
token = methodToPerformARefreshAndGenerateANewToken();
}
return token;
}
}
// ...
}
The idea there is when it tries to refresh the token, each thread specifies which token it is trying to refresh. A refresh is performed only if that is in fact the current token, and either way, the current token is returned.
You could use a ReentrantLock in place of my tokenMonitor, with locking and unlocking instead of synchronized blocks, but I much prefer plain synchronization when the scope is well contained, as in this case. Among other things, it's safer -- when you leave a synchronized block, you leave it; there is no possibility of failing to release the relevant monitor. The same cannot be said for lock objects.
This actually looks a problem that can be solved with versioning:
public class LockTest {
private int currentVersion = -1;
private Object token = null;
private synchronized int refreshToken(int requestorVersion) {
if (requestorVersion == currentVersion) {
try {
//do the actual refresh
Thread.sleep(1000);
token = new Object();
currentVersion++;
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
return currentVersion;
}
public VersionedToken takeToken() {
if (currentVersion == -1) {
refreshToken(-1);
}
return new VersionedToken(currentVersion);
}
public class VersionedToken {
private int version;
public VersionedToken(int version) {
this.version = version;
}
private void refresh() {
version = refreshToken(version);
}
private Object getToken() {
return token;
}
}
public static void main(String[] args) {
LockTest t = new LockTest();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
VersionedToken vtoken = t.takeToken();
Object token = vtoken.getToken();
try {
//do something with the token
}catch (Exception ex) {
//if token went bad - just refresh it and continue to work with it afterwords
vtoken.refresh();
token = vtoken.getToken();
}
}).start();
}
}
}