我有一个必须调用的外部 API,它需要验证 token 。调用 API 的应用程序将被线程化。此外,仅允许 5 个并发连接。我将使用固定线程池进行连接,但我在弄清楚如何处理过期/无效 token 时遇到了问题。我想要做的是,当一个线程遇到过期 token 时,阻止其他线程获取该 token ,直到刷新该 token 。我正在考虑使用 ReentrantLock 来执行此操作,但我不确定我的实现是否正确。
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();
}
}
为了测试,我睡了一些觉来模仿正在完成的工作。我不知道的主要事情是,当线程 A 在 getToken() 内部解锁时,线程 B 进入,但我们不知道 token 是否无效。所以 B 实际上可能得到了 A 必须找到的坏 token 。有没有好的方法来处理这个问题?或者说使用锁的想法是完全错误的?
最佳答案
我注意到的第一件事是您的代码没有正确同步。 testLock()
中的异常处理程序修改共享变量awaitTokenRefresh
相对于读取 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?
我想您真正想要避免的是当当前 token 无效时不必要的 token 刷新。恰好有一个线程应该刷新它;其他人应该简单地等待刷新,然后继续处理他们的事情。您的方法的问题在于线程没有好的方法来确定它们是否是第一个检测到过期的线程,因此应该负责刷新。这确实是有道理的,因为在多线程应用程序中,哪个线程首先执行任何操作的概念并不总是明确定义的。
是否使用锁还是同步是一个影响相对较小的实现细节。关键是您必须有一些共享状态来告诉线程它们建议刷新的 token 实际上是否仍然是当前的。我可能会像这样实现它:
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;
}
}
// ...
}
这个想法是,当它尝试刷新 token 时,每个线程指定它尝试刷新的哪个 token 。仅当这实际上是当前 token 时才会执行刷新,无论哪种方式,都会返回当前 token 。
您可以使用ReentrantLock
代替我的tokenMonitor
,使用锁定和解锁而不是同步块(synchronized block),但当范围被很好地包含时,我更喜欢简单的同步,就像在本例中一样。除此之外,它更安全——当你离开同步块(synchronized block)时,你就离开它;不存在无法释放相关监视器的可能性。对于锁对象则不能这样说。
关于java - 锁定一个方法直到另一个方法完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40051003/