2015-06-17 2 views
7

Voglio creare un'applicazione java, dove vogliamo effettuare chiamate di riposo per più utenti, con l'aiuto di un token di accesso. Sto usando 1 thread per utente. Il token di accesso, che sto utilizzando, è valido per 1 ora. Una volta scaduto il token, ricevo un errore 401 e devo aggiornare il token per tutti i thread e continuare. Sto pensando di utilizzare una variabile volatile che ho reso statico per aggiornare tutti i thread. Il mio requisito è che, nel momento in cui viene a sapere in uno dei thread che il token è scaduto, voglio che tutti i thread interrompano l'elaborazione e attendi che venga generato il nuovo token (questo richiede un paio di secondi). Anche una volta generato, il il token deve essere aggiornato automaticamente, senza che ogni thread abbia esito negativo a causa del token scaduto. ciCondivisione di dati tra più thread java e ottenere il valore aggiornato

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

} 

} 

è un approccio migliore a questo problema:

seguito è riportato un esempio di codice che ho scritto? Il codice sopra non soddisfa il mio caso d'uso, poiché una volta che un thread inizia a generare un nuovo token, tutti gli altri thread non vengono messi in pausa. Richiesta di suggerire alcuni miglioramenti ..

+1

mi corregga se sto compagno sbagliato, si vuole in sostanza tutte le discussioni aspettare fino a quando uno di essi aggiorna il tuo token vero? – nafas

+1

sì..questo è esattamente ciò che voglio ... nel caso in cui uno dei thread ottenga un nuovo token, voglio che quel valore sia aggiornato per tutti i thread, indipendentemente da dove sia l'esecuzione del singolo thread, non dovrebbe provare mai per usare il vecchio token e fallire e creare nuovamente il token ... – Anupam

risposta

5

È possibile inserire il token in una AtomicReference e utilizzare un Semaphore per mettere in pausa i fili:

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() utilizza un atomico compareAndSet su tokenRef per assicurare che solo un thread si aggiorna il token, e quindi chiama drainPermits() su semaphore in modo che altri thread attenda fino a quando il token non viene aggiornato. getToken() restituisce il token se non è null, altrimenti attende sul semaphore - questo è fatto in un ciclo perché è possibile che un thread dovrà girare per alcuni cicli tra tokenRef essere impostato su null e drainPermits() essere chiamato sul semaphore.


Edit: Modificata la firma del refreshToken(Token oldToken) in modo che il vecchio token viene passato piuttosto che essere letto all'interno del metodo - questo è quello di evitare una situazione in cui RESTService_A rinfresca il token, RESTService_B ottiene un 401 con il vecchio token scaduto, quindi RESTService_B chiama refreshToken dopo che la chiamata di RESTService_A a refreshToken è stata completata, con conseguente aggiornamento del token due volte. Con la nuova firma, RESTService_B passerà il vecchio token scaduto e pertanto la chiamata compareAndSet avrà esito negativo quando il vecchio token non riesce a corrispondere al nuovo token, con conseguente chiamata solo una volta a refreshToken.

+0

Questo è esattamente il tipo di soluzione che stavo cercando, qualcosa a cui non posso pensare facilmente ... grazie mille ... – Anupam

+0

Grazie mille signore ... Hai risolto completamente il mio problema ... Questo è perfetto per me..:) – Anupam

1

È possibile utilizzare il seguente schema, accedendo al token utilizzando solo il suo getter e chiamando loadToken quando si riceve la risposta all'errore.

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

Per affrontare il problema di interrompere le discussioni, si può chiamare getToken() dovunque si vuole fermare la Thread che blocca automaticamente nel caso in cui il caricamento del token è attualmente attivo.

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

Grazie mille..questo rende il design molto migliore ... Proverò a fare ulteriori miglioramenti su questo. – Anupam

1

Poiché è necessario eseguire due operazioni (chiamate http e token di aggiornamento), è possibile provare il controllo bidirezionale.

uno per verificare se il token is expired or not e l'altro è per verificare se qualsiasi altro thread sta cercando di update the token.

per dimostrare l'idea qui è un piccolo codice (il suo abit modo sporco di farlo in modo che potrebbe essere necessario un po 'di pulizia)

... 
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... 
    } 

}