在多个java线程之间共享数据并获取更新的值

时间:2015-06-17 13:37:38

标签: java multithreading thread-safety

我想创建一个java应用程序,我们希望在访问令牌的帮助下为多个用户进行休息调用。我每个用户使用1个线程。我正在使用的访问令牌有效期为1小时。一旦令牌过期,我将收到401错误,并且必须更新所有线程的令牌,然后继续。我正在考虑使用一个volatile变量,我已经使静态更新所有线程。我的要求是,当我在其中一个线程中知道令牌已过期时,我希望所有线程停止处理并等待生成新令牌(这需要几秒钟)。也一旦生成, token应该自动更新,而不会因为令牌过期而导致每个线程失败。

以下是我编写的示例代码:

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";

}

 }

有没有更好的方法解决这个问题?上面的代码不满足我的用例,因为一旦线程开始生成新令牌,所有其他线程都不会被暂停。请求建议一些改进..

3 个答案:

答案 0 :(得分:7)

您可以将令牌放在AtomicReference中并使用Semaphore暂停线程:

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()使用compareAndSet上的原子tokenRef来确保只有一个线程会刷新令牌,然后调用drainPermits()上的semaphore来导致其他线程要等到令牌刷新。 getToken()如果不是null则返回令牌,否则等待semaphore - 这是在循环中完成的,因为线程可能需要旋转几个周期tokenRefnull设置为drainPermits()semaphorerefreshToken(Token oldToken)调用。

编辑:修改了refreshToken的签名,以便传入旧令牌而不是在方法内部读取 - 这是为了防止RESTService_A刷新令牌的情况,RESTService_B得到401旧版本过期令牌,然后RESTService_B在RESTService_A调用refreshToken完成后调用compareAndSet,导致令牌刷新两次。使用新签名时,RESTService_B将传入旧的过期令牌,因此当旧令牌无法匹配新令牌时,refreshToken调用将失败,导致reddit-post-list仅被调用一次。

答案 1 :(得分:1)

您可以使用以下模式,仅使用其getter访问令牌并在收到错误响应时调用loadToken

class TokenGenerator {
    private String token = null;
    public synchronized String getToken() {
        if (token == null) {
            loadToken();
        }
        return token;
    }
    public synchronized void loadToken() {
        token = "load here";
    }           
}

要解决暂停线程的问题,您可以在需要暂停getToken()时调用Threadclass Task implements Runnable{ private String name; private TokenGenerator tokenGenerator; public Task(String name, TokenGenerator tokenGenerator) { this.name = name; this.tokenGenerator = tokenGenerator; } @Override public void run() { getResponse(tokenGenerator.getToken()); } 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.loadToken(); } } 会自动阻止,以防加载令牌当前处于活动状态。

MutableProperty<Bool>

答案 2 :(得分:1)

由于您需要做两件事(http调用和更新令牌),您可以尝试双向检查。

检查令牌is expired or not和另一个是否检查其他线程是否正在尝试update the token

来证明这里的想法是一个小代码(它的abit脏的方式,所以它可能需要一些清理)

...
private string token
private volatile static isTokenExpired=false   //checking if the token is expired or not
private volatile static waitingForTokenRefresher=false;  //checking if we should wait for update.

@Override
public void run(){
  while(tokenisExpired){
      //wait
  }

  //http calls find out if token is good to go
  //check if no one else uses the token:
  if( token is actually expired){
   if(!waitingForTokenRefresher){
       isTokenExpired=true;
       waitingForTokenRefresher=true;
       //refresh token
       waitingForTokenRefresher=false
       isTokenExpired=false;
   }
  }
   while(!waitingForTokenRefresher){
     //wait...
   }

}