问题描述
我想创建一个Java应用程序,在这里我们想借助访问令牌对多个用户进行rest调用.我正在为每个用户使用1个线程.我正在使用的访问令牌有效期为1个小时.一旦令牌过期,我将收到401错误消息,并且必须为所有线程更新令牌,然后继续.我正在考虑使用我已将其设为静态的volatile变量来更新所有线程.我的要求是,在我知道其中一个线程令牌已过期的那一刻,我希望所有线程停止处理并等待直到生成新令牌(这需要几秒钟).令牌应该自动更新,而每个线程都不会由于令牌过期而失败.
I want to create a java application , where we want to make rest calls for multiple users , with the help of an access token. I am using 1 thread per user . The access token, that I am using , is valid for 1 hour.Once the token expires , I will get an 401 error , and have to update the token for all the threads , and continue. I am thinking of using a volatile variable which I have made static to update all the threads. My requirement , is , the moment I get to know in one of threads that the token has expired , I want all the threads to stop processing and wait till the new token is generated(this takes couple of seconds).Also once generated , the token should be updated automatically , without each thread failing due to expired token.
下面是我编写的示例代码:
Below is a sample code that I have written :
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Sample {
public static void main(String[] args) {
String[] myStrings = { "User1" , "User2" , "User3" };
ScheduledExecutorService scheduledExecutorService = Executors
.newScheduledThreadPool(myStrings.length);
TokenGenerator.getToken();
for(String str : myStrings){
scheduledExecutorService.scheduleAtFixedRate(new Task(str), 0, 5, TimeUnit.SECONDS);
}
}
}
class Task implements Runnable{
private String name;
public Task(String name){
this.name = name;
}
@Override
public void run() {
getResponse(TokenGenerator.token);
}
private void getResponse(String token) {
// Make http calls
// if token expire , call getToken again. Pause all the running threads , and
// update the token for all threads
TokenGenerator.getToken();
}
}
class TokenGenerator {
public static volatile String token;
public static void getToken() {
token = "new Token everytime";
}
}
是否有更好的方法来解决此问题?上面的代码无法满足我的用例,因为一旦线程开始生成新令牌,其他所有线程都不会暂停.要求提出一些改进建议.
Is there a better approach to this problem? The above code does not satisfies my use case, since once a thread starts generating a new token, all other threads are not being paused. Request to suggest some improvements..
推荐答案
您可以将令牌放入 AtomicReference 并使用信号量以暂停线程:
You can put the token in an AtomicReference and use a Semaphore to pause the threads:
public class TokenWrapper {
private final AtomicReference<Token> tokenRef = new AtomicReference<>(null);
private final Semaphore semaphore = new Semaphore(Integer.MAX_VALUE);
public TokenWrapper() {
Token newToken = // refresh token
tokenRef.set(newToken);
}
public Token getToken() {
Token token = null;
while((token = tokenRef.get()) == null) {
semaphore.acquire();
}
return token;
}
public Token refreshToken(Token oldToken) {
if(tokenRef.compareAndSet(oldToken, null)) {
semaphore.drainPermits();
Token newToken = // refresh token
tokenRef.set(newToken);
semaphore.release(Integer.MAX_VALUE);
return newToken;
} else return getToken();
}
}
public class RESTService {
private static final TokenWrapper tokenWrapper = new TokenWrapper();
public void run() {
Token token = tokenWrapper.getToken();
Response response = // call service with token
if(response.getStatus == 401) {
tokenWrapper.refreshToken(token);
}
}
}
refreshToken()
在tokenRef
上使用原子compareAndSet
来确保只有一个线程将刷新令牌,然后在semaphore
上调用drainPermits()
促使其他线程等待直到刷新令牌. getToken()
如果不是null
,则返回令牌,否则等待semaphore
-这是在循环中完成的,因为在tokenRef
设置为之前,线程可能必须旋转几个周期.在semaphore
上调用null
和drainPermits()
.
refreshToken()
uses an atomic compareAndSet
on tokenRef
to ensure that only one thread will refresh the token, and then calls drainPermits()
on the semaphore
to cause other threads to wait until the token is refreshed. getToken()
returns the token if it isn't null
, else waits on the semaphore
- this is done in a loop because it's possible that a thread will have to spin for a few cycles between tokenRef
being set to null
and drainPermits()
being called on semaphore
.
修改了refreshToken(Token oldToken)
的签名,以便传递旧令牌而不是在方法内部读取-这是为了防止RESTService_A刷新令牌,RESTService_B收到带有旧过期令牌的401的情况,然后在RESTService_A对refreshToken
的调用完成之后,RESTService_B调用refreshToken
,从而导致令牌被刷新两次.使用新的签名,RESTService_B将传递旧的过期令牌,因此,当旧令牌与新令牌不匹配时,compareAndSet
调用将失败,从而导致refreshToken
仅被调用一次.
Modified the signature of refreshToken(Token oldToken)
so that the old token is passed in rather than being read inside of the method - this is to prevent a situation where RESTService_A refreshes the token, RESTService_B gets a 401 with the old expired token, and then RESTService_B calls refreshToken
after RESTService_A's call to refreshToken
has completed, resulting in the token being refreshed twice. With the new signature, RESTService_B will pass in the old expired token, and so the compareAndSet
call will fail when the old token fails to match the new token, resulting in refreshToken
only being called once.
这篇关于在多个Java线程之间共享数据并获取更新的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!