簡體   English   中英

在多個java線程之間共享數據並獲取更新的值

[英]Sharing data between multiple java threads and get the updated value

我想創建一個java應用程序,我們希望在訪問令牌的幫助下為多個用戶進行休息調用。 我每個用戶使用1個線程。 我正在使用的訪問令牌有效期為1小時。一旦令牌過期,我將收到401錯誤,並且必須更新所有線程的令牌,然后繼續。 我正在考慮使用一個volatile變量,我已經使靜態更新所有線程。 我的要求是,當我在其中一個線程中知道令牌已過期時,我希望所有線程都停止處理並等待生成新令牌(這需要幾秒鍾)。也一旦生成,應該自動更新令牌,而不會因為過期令牌而導致每個線程失敗。

下面是我編寫的示例代碼:

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

}

 }

有沒有更好的方法來解決這個問題? 上面的代碼不滿足我的用例,因為一旦線程開始生成新令牌,所有其他線程都不會被暫停。 請求建議一些改進..

您可以將令牌放在AtomicReference中並使用信號量來暫停線程:

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()使用原子compareAndSettokenRef ,以確保只有一個線程將刷新令牌,然后調用drainPermits()semaphore ,以使其他線程等待,直到令牌被刷新。 如果令牌不為null ,則getToken()返回令牌,否則等待semaphore - 這是在循環中完成的,因為線程可能必須在tokenRef設置為nulldrainPermits()之間旋轉幾個周期呼吁semaphore


編輯:修改了refreshToken(Token oldToken)的簽名,以便傳入舊令牌而不是在方法內部讀取 - 這是為了防止RESTService_A刷新令牌的情況,RESTService_B獲取帶有舊過期令牌的401,然后RESTService_B調用refreshToken RESTService_A對通話后refreshToken已完成,導致令牌被刷新兩次。 使用新簽名時,RESTService_B將傳入舊的過期令牌,因此當舊令牌無法匹配新令牌時, compareAndSet調用將失敗,導致refreshToken僅被調用一次。

您可以使用以下模式,僅在使用其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()只要你想要暫停Thread ,它將自動阻止,以防加載令牌當前處於活動狀態。

class 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();
    }
}

由於您需要做兩件事(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...
   }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM