2012-02-20 21 views
12

Come suggerisce il titolo, sto cercando un'implementazione confronto-e-swap, ma con maggiore di confronto:Maggiore di scambio confrontare-e-

if(newValue > oldValue) { 
    oldValue = newValue; 
} 

dove oldValue è un certo stato condiviso globale e newValue è privato per ogni thread, senza fare questo:

synchronized(locker) { 
    if(newValue > oldValue) { 
     oldValue = newValue; 
    }  
} 

perché voglio una soluzione non-blocking. Dallo studio codici sorgente di altre operazioni non bloccanti, sono venuto con questa (supponendo che i valori sono numeri interi):

AtomicInteger oldValue; // shared global variable 

... 

public boolean GreaterThanCAS(int newValue) { 

    while(true) { 
     int local = oldValue; 
     if(local == oldValue) { 
      if(newValue > local) { 
       if(oldValue.compareAndSet(local, newValue) { 
        return true; // swap successful 
       } // else keep looping 
      } else { 
       return false; // swap failed 
      } 
     } // else keep looping 
    } 
} 

quando // else keep looping accade, significa che un altro thread ha cambiato il oldValue nel frattempo quindi ho bisogno di fare un ciclo e riprovare.

Questa implementazione è corretta (thread-safe)?

+0

Questo è solo controllo per vedere se la commutazione discussione si è verificato tra assegnando la variabile 'local' e controllo per vedere se sono uguali La commutazione del thread potrebbe verificarsi dopo l'istruzione if. Quindi no, questo non è thread-safe, ma senza bloccare non sono sicuro se troverai una soluzione. – Shaded

+0

@Shaded: anche la chiamata 'oldValue.compareAndSwap (local, newValue)' restituisce false se il 'oldValue' non è uguale a' local', quindi controlla anche qui. – Tudor

+0

Non è necessario il primo confronto di uguaglianza. Solo "se (newValue> local) oldValue.CAS (local, newValue) else repeat" è sufficiente – BegemoT

risposta

7

Non vedo problemi con l'implementazione, , a condizione che nessun thread diminuisca mai il valore di AtomicInteger. Se lo fanno, il tuo codice è aperto alle condizioni di gara.

Si noti che il codice può essere semplificata come segue:

public boolean GreaterThanCAS(int newValue) { 
    while(true) { 
     int local = oldValue.get(); 
     if(newValue <= local) { 
      return false; // swap failed 
     } 
     if(oldValue.compareAndSet(local, newValue)) { 
      return true; // swap successful 
     } 
     // keep trying 
    } 
} 
+0

Grazie. Hai ragione che il decremento causerebbe problemi, ma per il mio scenario, il valore di 'oldValue' può cambiare solo eseguendo questa operazione.Grazie anche per il suggerimento di semplificazione. Ora che ci penso, quel "se" era davvero ridondante. – Tudor

+0

Penso che il tuo codice usando '<=' per il confronto in un metodo con 'GreaterThan' nel nome è un po 'strano. –

+0

@ TomHawtin-tackline: trovo questa struttura più leggibile rispetto alle dichiarazioni "if" nidificate originali. Se si obietta al '<=', si potrebbe facilmente definirlo come 'if (! (NewValue> local))'. Personalmente non trovo questa versione riformulata più o meno chiara di quella che ho inserito nella risposta. – NPE

2

avrei ri scriverlo a guardare più come:

while(true) { 
    int local = oldValue.get(); 
    if(newValue > local){ 
     if(oldValue.compareAndSwap(local, newValue) { 
       return true; // swap successful 
     } // else keep looping 
    }else 
     return false; 
} 

La verifica di equivalenza prima che il maggiore di controllo è ridondante.

Altrimenti dovrebbe funzionare correttamente.

10

Dal Java 8 questo può essere semplificato con l'utilizzo di updateAndGet:

public boolean greaterThanCAS(int newValue) { 
    return oldValue.updateAndGet(x -> x < newValue ? newValue : x) == newValue; 
} 

Si noti che questo sarebbe tornato vero anche nel caso in cui vecchi e nuovi valori sono uguali. Provare a @Adam's answer se questo non è il comportamento desiderato.

+3

'x hengxin

+0

@hengxin, grazie, risolto. – Vadzim

+0

Dovrebbe invece usare 'updateAndGet'? –

2

@Vadzim, avrei commentato il tuo post, ma StackOverflow dice che non ho abbastanza punti per postare commenti. La tua risposta è quasi corretta, ma la tua funzione restituirà sempre false perché getAndUpdate restituisce sempre il valore precedente, o 'x' nel tuo caso. Credo che tutto quello che avrebbe bisogno di fare è sostituire il vostro ultimo '==' con '<', ad esempio:

// return true if the assignment was made, false otherwise 
public boolean greaterThanCAS(int newValue) { 
    return oldValue.getAndUpdate(x -> x < newValue ? newValue : x) < newValue; 
} 
+0

Grazie per la segnalazione. Anche questa risposta è corretta, ma ho risolto il mio con passando a 'updateAndGet'. Nota che ora le risposte differiscono nella gestione del caso quando i valori vecchi e nuovi sono uguali. Dipende dal contesto in cui il comportamento si adatta meglio. – Vadzim