6

zu implementieren Ich bin relativ neu in Scala und funktionale Programmierung, und ich mag die Idee, dass mit unveränderlichen Objekten kann ich viele Thread Sicherheit Fallstricke vermeiden. Eine Sache verfolgt mich immer noch, und es ist das klassische Beispiel, das verwendet wird, um Thread-Sicherheit - den geteilten Zähler zu unterrichten.Funktionelle Möglichkeit, einen Thread Safe Shared Counter

Ich frage mich, ob es möglich wäre, einen threadsicheren Zähler (ein Anforderungszähler in diesem Beispiel) zu implementieren, unveränderliche Objekte und funktionale Konzepte zu verwenden und Synchronisierung vollständig zu vermeiden.

So Referenz hier sind zunächst die klassischen wandelbaren Versionen des Zählers (entschuldigen Sie mich für die öffentliche Membervariable, nur aus Gründen der Kürze der Beispiele)

Mutable, Nicht Thread sichere Version:

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

Mutable Classic Faden sichere Version: (so hoffe ich ...)

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

Ich frage mich, ob es eine Möglichkeit gibt, unveränderliche Objekte und flüchtige Variablen zu verwenden, um Thread-Sicherheit ohne Synchronisation zu erreichen.

Also hier war mein naive Versuch. Die Idee ist, ein unveränderliches Objekt für den Zähler zu haben, und einfach den Verweis darauf zu ersetzen, indem eine flüchtige Variable verwendet wird. Fühlt sich fischig an, aber einen Versuch wert.

Halter:

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

Modifiziertes Servlet:

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

Ich habe ein starkes Gefühl, das ist auch nicht Thread-sicher, wie ich aus Inkrementierer bin zu lesen, und vielleicht bekommen ein veralteter Wert (z. B. wenn die Referenz bereits durch einen anderen Thread ersetzt wurde). Falls es in der Tat nicht Thread-sicher ist, dann frage ich mich, ob es überhaupt einen "funktionalen" Weg gibt, ein solches Counter-Szenario ohne Locking/Synchronisation zu handhaben.

Also meine Frage (n) sind

  1. Ist das Gewinde durch Zufall sicher?
  2. Wenn ja, warum?
  3. Wenn nicht, gibt es überhaupt eine Möglichkeit, einen solchen Zähler ohne Synchronisierung zu implementieren?

Obwohl der Beispielcode oben in Java ist, antwortet in Scala sind natürlich auch

Antwort

13

Ist das Thread-sicher durch Zufall willkommen?

Nein, es sei denn, Sie haben das unveränderliche Objekt bereits in einem synchronisierten Block erstellt. Dies ist nicht Thread-sicher. Es gibt Chancen, ein beschädigtes unveränderliches Objekt unter Thread-Race-Bedingung zu erstellen.

Und um die gleiche Funktionalität zu erreichen, können Sie AtomicInteger verwenden, die explizite Synchronisation vermeidet.

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

Interessant, ich war sicher AtomicInteger verwendet Synchronisation intern, so dass ich es nicht einmal als eine Antwort betrachtete, aber mit Blick auf den Quellcode scheint es, dass es andere Wege (nativen Code) verwendet, um das Ziel zu erreichen: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java –

4

Thread-Sicherheit von immutables sieht viel mehr so ​​aus.

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

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

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

Wenn Sie mutably gearbeitet haben, würden Sie sehr in Versuchung 2 haben Thema eine veränderbare Liste ändern, die dann nicht Thread 1-Index machen könnte. Oder Sie müssten die Daten kopieren, was sehr teuer sein könnte, wenn Sie keine Sammlungen hätten, die so oft wie möglich wiederverwendet werden sollen (und wenn der Vektor länger war). Oder Sie müssten große Blöcke in beiden Threads synchronisieren, anstatt nur atomweise Daten zu erhalten und/oder zu setzen.

Sie müssen immer noch irgendwie synchronisieren, und Sie müssen möglicherweise mit veralteten Kopien von Daten umgehen. Aber Sie nicht müssen verfolgen, wer eine Kopie von welcher Datenstruktur hat und synchronisieren Sie alle wie verrückt, weil diese Daten aus unter Ihnen ändern und Ausnahmen überall werfen können.