6

Sono relativamente nuovo a Scala e alla programmazione funzionale, e mi piace l'idea che usando oggetti immutabili posso evitare molte insidie ​​nella sicurezza dei thread. Una cosa mi perseguita ancora ed è l'esempio classico usato per insegnare la sicurezza del thread: il contatore condiviso.Modo funzionale per implementare un contatore condiviso thread-safe

Mi chiedevo se sarebbe stato possibile implementare un contatore thread-safe (un contatore di richieste in questo esempio), utilizzando oggetti immutabili e concetti funzionali ed evitare completamente la sincronizzazione.

Quindi, per riferimento qui sono prima le versioni classiche mutevoli del contatore (scusate per la variabile membro pubblico, solo per brevità degli esempi)

Mutevole, non filo versione a sicurezza:

public class Servlet extends HttpServlet { 

    public int requestCount = 0; 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    requestCount++; //thread unsafe 
    super.service(req, res); 
    } 
} 

Mutevole, filo classica versione a sicurezza: (o almeno così spero ...)

public class Servlet extends HttpServlet { 

    public volatile int requestCount = 0; 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    synchronized (this) { 
     requestCount++; 
    } 
    super.service(req, res); 
    } 
} 

Mi chiedevo se esiste un modo per utilizzare oggetti immutabili e variabili volatili per ottenere la sicurezza del thread senza sincronizzazione.

Quindi ecco il mio ingenuo tentativo. L'idea è di avere un oggetto immutabile per il contatore, e basta sostituire il riferimento ad esso, usando una variabile volatile. Sembra pescoso, ma vale la pena sparare.

Holder:

public class Incrementer { 
    private final int value; 
    public Incrementer(final int oldValue) { 
    this.value = oldValue + 1; 
    } 

    public Incrementer() { 
    this.value = 0; 
    } 

    public int getValue() { 
    return value; 
    } 
} 

servlet Modificato:

public class Servlet extends HttpServlet { 

    public volatile Incrementer incrementer = new Incrementer(); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    incrementer = new Incrementer(incrementer.getValue()); 
    super.service(req, res); 
    } 
} 

Ho una forte sensazione che questo non è anche thread-safe, come sto leggendo da incrementatore, e potrebbe ottenere un valore scaduto (ad es. se il riferimento era già stato sostituito da un altro filo). Nel caso in cui non sia effettivamente thread-safe, allora mi chiedo se non c'è affatto un modo "funzionale" per gestire un tale scenario contatore senza blocco/sincronizzazione.

Quindi la mia domanda (s) sono

  1. È questo thread-safe per caso?
  2. Se sì, perché?
  3. In caso contrario, esiste un modo per implementare tale contatore senza eseguire la sincronizzazione?

Anche se il codice di esempio di cui sopra è in Java, risponde a Scala sono ovviamente i benvenuti

risposta

13

È questo thread-safe per caso?

No, a meno che non sia già stato creato l'oggetto immutabile in un blocco sincronizzato, questo non è thread-safe. Ci sono possibilità di creare un oggetto immutabile corrotto in condizioni di competizione.

E per ottenere la stessa funzionalità è possibile utilizzare AtomicInteger che evita la sincronizzazione esplicita.

public class Servlet extends HttpServlet { 

    public AtomicInteger incrementer = new AtomicInteger (0); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    int newValue = incrementer.incrementAndGet(); 
    super.service(req, res); 
    } 
} 
+0

Interessante, ero sicuro che AtomicInteger utilizza internamente la sincronizzazione, quindi non l'ho nemmeno considerato come una risposta, ma guardando il codice sorgente sembra che stia usando altri modi (codice nativo) per raggiungere l'obiettivo: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java –

4

La sicurezza del filo da immutables sembra molto più simile.

val x = AtomicReference(Vector("salmon", "cod")) 

// Thread 1 
val y = x.get 
println(y(y.length-1)) 

// Thread 2 
x.getAndSet(x.get.tail) 

se si stesse lavorando mutably, si sarebbe fortemente tentato di avere Thread 2 altera un elenco mutevole, che potrebbe poi fare index filettatura 1 di sicuro. Oppure dovresti copiare i dati, che potrebbero essere molto costosi se non avessi collezioni intese a riutilizzarle quanto più possibile (e se il vettore era più lungo). O dovresti sincronizzare i blocchi di grandi dimensioni in entrambi i thread invece di ottenere e/o ottenere automaticamente i tuoi dati.

È ancora necessario eseguire la sincronizzazione in qualche modo e potrebbe essere necessario gestire copie di dati non aggiornate. Ma tu non devi tenere traccia di chi ha una copia di quale struttura dati e sincronizzare tutti come matti perché questi dati potrebbero cambiare da te e generare eccezioni ovunque.

Problemi correlati