2016-04-12 13 views
2

Ich modifiziere eine Java-Server-Software. Die gesamte Anwendung ist single-threaded. Eine meiner Änderungen nimmt viel Zeit in Anspruch, daher habe ich mich entschieden, asynchron zu arbeiten, um das Einfrieren des Hauptthreads zu vermeiden.Java thread safe locking

Dies ist ein Beispiel für den ursprünglichen Code (nicht der eigentliche Code, nur ein Beispiel):

public class Packet { 
    private final byte[] data = new byte[1024]; 

    public void setData(int index, byte data) { 
     this.data[index] = data; 
    } 

    public byte getData(int index) { 
     return data[index]; 
    } 

    public void sendPacket(ClientConnection clientConnection) { 
     clientConnection.sendPacket(data); 
    } 
} 

Derzeit ist dieses mein Code (in den Kommentaren sehen):

public class Packet { 
    private final byte[] data = new byte[1024]; 

    public void setData(int index, byte data) { 
     synchronized (this) { 
      this.data[index] = data; 
     } 
    } 

    public byte getData(int index) { 
     return data[index]; 
    } 

    public void sendPacket(final ClientConnection clientConnection) { 
     //This state of data should be sent 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       //The thread is now running 
       //The main-thread can move on 
       //The main-thread can also modify data now because we are not inside the synchronized block 
       //But it should not because the state of data when the method sendPacket was called should be sent 
       synchronized (Packet.this) { 
        thisTakesMuchTime(data); 
        clientConnection.sendPacket(data); 
       } 
      } 
     }).start(); 
    } 
} 

Was Ich suche eigentlich etwas ist so:

public class Packet { 
    private final byte[] data = new byte[1024]; 

    public void setData(int index, byte data) { 
     //wait for unlock 
     this.data[index] = data; 
    } 

    public byte getData(int index) { 
     return data[index]; 
    } 

    public void sendPacket(final ClientConnection clientConnection) { 
     //lock 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       thisTakesMuchTime(data); 
       clientConnection.sendPacket(data); 
       //unlock 
      } 
     }).start(); 
    } 
} 

Die Frage: Was ist die beste Umsetzung von su ch eine Sperre in Java? Soll ich es selbst mit einer AtomicInteger zum Beispiel machen.

Bearbeiten: Sehen Sie sich meine Antwort für meine aktuelle Implementierung.

+1

es ist nicht ganz klar, was Sie hier fragen. Was ist die Reihenfolge der Operationen? rufe ich zuerst 'Packet.setData' und dann' Packet.sendPacket' an? Was möchten Sie tun, wenn das Paket gesendet wurde? –

+0

Im Allgemeinen würde ich vermeiden, einen eigenen Sperrcode zu implementieren: erfinde das Rad nie neu, es sei denn, du musst es unbedingt tun. Und es gibt Dinge wie ReentrantLock ... Zeug, das gut dokumentiert ist; und von vielen anderen Leuten benutzt. Dinge "selbst" zu tun, birgt immer das Risiko, es falsch zu machen. – GhostCat

+0

Ihre Sperre stellt sicher, dass Sie nicht in ein Paket schreiben können, während es gesendet wird, und Sie können es nicht senden, während Sie es schreiben (aus verschiedenen Threads). Das scheint nicht die beste Option zu sein. Sie sollten einen Pool für die 'clientConnection' implementieren, da so viele Pakete gleichzeitig übertragen werden können. – OldCurmudgeon

Antwort

2

Sie können eine Kopie Ihrer Daten erstellen und die Kopie senden, um Nebenläufigkeit zu vermeiden.

public class Packet { 
    private final byte[] data = new byte[1024]; 

    public void setData(final int index, final byte data) { 
     this.data[index] = data; 
    } 

    public byte getData(final int index) { 
     return data[index]; 
    } 

    public void sendPacket(final ClientConnection clientConnection) { 
     byte[] dataToSend = new byte[1024]; 
     System.arraycopy(data, 0, dataToSend, 0, 1024); 
     new Thread(new Runnable() { 
      @Override public void run() { 
       clientConnection.sendPacket(dataToSend); 
      } 
     }).start(); 
    } 
} 

CopyOnWriteArrayList Verwendung ist analog zu dem Code unten, die auch Gleichzeitigkeit vermeidet aber nicht so effizient (vorausgesetzt Sie setData häufiger als sendPacket Aufruf werde):

public class Packet { 
    private byte[] data = new byte[1024]; 

    public void setData(final int index, final byte data) { 
     byte[] newData = new byte[1024]; 
     System.arraycopy(data, 0, newData, 0, 1024); 
     newData[index] = data; 
     this.data = newData; 
    } 

    public byte getData(final int index) { 
     return data[index]; 
    } 

    public void sendPacket(final ClientConnection clientConnection) { 
     new Thread(new Runnable() { 
      @Override public void run() { 
       clientConnection.sendPacket(data); 
      } 
     }).start(); 
    } 
} 
+0

Ja, das würde funktionieren, aber ich möchte vermeiden, eine Kopie auf dem Haupt-Thread zu machen. – stonar96

+0

@AntonK., Was meinst du? "Die gesamte Anwendung ist single-threaded." – ericbn

+0

Sorry, mein Fehler ... –

1

Die einfachste sperren Sie Kann verwenden ist die Reentrant Lock, Reentrant bedeutet, dass, wenn Sie versuchen, die Sperre zu erwerben, wenn Sie es bereits haben, wird der Vorgang erfolgreich sein.

in Ihrem Code zu erreichen, das Einfädeln Sie wünschen, werden Sie auch wait() und notify() zu verwenden, haben den Haupt-Thread zu blockieren, bis Ihr Kind Thread die Sperre erworben hat:

public class Packet { 
    private final ReentrantLock lock = new ReentrantLock(); 
    private final byte[] data = new byte[1024]; 

    public void setData(int index, byte data) { 
     lock.lock(); //wait for unlock 
     try { 
      this.data[index] = data; 
     } finally { 
      lock.unlock(); 
     } 
    } 

    public byte getData(int index) { 
     return data[index]; 
    } 

    public void sendPacket(final ClientConnection clientConnection) { 
     Thread thread = new Thread(new Runnable() { 
      @Override 
      public void run() { 
       lock.lock(); //lock 
       try { 
        synchronized(this) { 
         this.notify(); 
        } 

        thisTakesMuchTime(data); 
        clientConnection.sendPacket(data); 
       } finally { 
        lock.unlock(); //unlock 
       } 
      } 
     }).start(); 

     synchronized(thread) { 
      try { 
       thread.wait(); 
      } catch (InterruptedException e) { 
       //handle 
      } 
     } 
    } 
} 

auch eine ExecutorService prüfen, mit und keine rohen Thread-Objekte erstellen.

+0

Ich glaube, du hast das Schloss in 'setData' verpasst, oder? Aber es ist nicht funktional identisch. – stonar96

+0

Es gab einige Copy-Paste-Probleme, die ich seitdem aktualisiert habe. – Danikov

+0

Okay, aber der erste Code ist immer noch nicht funktional identisch mit dem zweiten Code. Im zweiten Code kann der Haupt-Thread nach dem Aufruf von 'sendPacket' noch immer' setData' aufrufen, bevor das Paket gesendet wird, was eigentlich mein Problem ist. Der erste Code würde dieses Problem lösen. – stonar96

0

Was ist die beste Implementierung eines solchen Schlosses in Java? Soll ich es zB selbst mit einem AtomicInteger machen.

Ich denke @ Ericbn Antwort würde funktionieren ok. Eine Kopie der Daten mit dem Haupt-Thread, aber immer noch innerhalb von Packet zu greifen ist in Ordnung.

Sie sind jedoch besorgt über den 1k-Puffer? Die echte Kosten hier erstellt nicht eine Kopie der Daten im Haupt-Thread, es ist Forking der Thread jedes Mal Sie gehen, um das Paket zu senden. Dies ist im Vergleich zur Objekterstellung enorm teuer. Ich würde einen Thread-Pool verwenden und Paketaufträge an ihn senden.

// you might want this to be bounded so you don't queue up too many packets 
private final ExecutorService threadPool = Executors.newSingleThreadExecutor(); 
... 

public void sendPacket(ClientConnection clientConnection) { 
    byte[] dataToWrite = new byte[data.length]; 
    System.arraycopy(data, 0, dataToWrite, 0, dataToWrite.length); 
    threadPool.submit(new PacketToWrite(dataToWrite, clientConnection)); 
    // you can clear or reset the `data` array now 
} 

private static class PacketToWrite implements Runnable { 
    private final byte[] dataToWrite; 
    private final ClientConnection clientConnection; 
    public PacketToWrite(byte[] dataToWrite, ClientConnection clientConnection) { 
     this.dataToWrite = dataToWrite; 
     this.clientConnection = clientConnection; 
    } 
    public void run() { 
     thisTakesMuchTime(data); 
     clientConnection.sendPacket(data); 
    } 
} 

Sie senden Daten über das Netzwerk, so dass die zusätzliche Objektbandbreite nichts im Vergleich zur Netzwerkverzögerung ist.

+0

Es würde mit einer Kopie des Datenarrays funktionieren. Aber das reale Datenfeld ist viel größer und das Paket wird sehr oft gesendet. Ich möchte also wirklich vermeiden, das Array zu kopieren. Ich habe jetzt bereits eine Lösung mit einer eigenen Schlossimplementierung. Ich weiß, das würde meinen Haupt-Thread einfrieren, aber nur in seltenen Fällen ('setData' wird selten genannt). – stonar96

+0

Ich habe gerade meine aktuelle Implementierung als Antwort gepostet. – stonar96

0

Meine aktuelle Implementierung:

Paket:

public class Packet { 
    private final Lock lock = new Lock(); 
    private final byte[] data = new byte[1024]; 

    public void setData(int index, byte data) { 
     lock.waitUntilUnlock(); 
     this.data[index] = data; 
    } 

    public byte getData(int index) { 
     return data[index]; 
    } 

    public void sendPacket(final ClientConnection clientConnection) { 
     lock.lock(); 
     new Thread(new Runnable() { // I use an ExecutorService 
      @Override 
       public void run() { 
       thisTakesMuchTime(data); 
       clientConnection.sendPacket(data); 
       lock.unlock(); 
      } 
     }).start(); 
    } 
} 

Lock:

public class Lock { 
    private final AtomicInteger lockCount = new AtomicInteger(); 

    public void lock() { // Main thread 
     lockCount.incrementAndGet(); 
    } 

    public synchronized void unlock() { 
     lockCount.decrementAndGet(); 
     notifyAll(); 
    } 

    public synchronized void waitUntilUnlock() { // Main thread 
     try { 
      while (lockCount.get() > 0) { 
       wait(); 
      } 
     } catch (InterruptedException e) { 
      Thread.currentThread().interrupt(); 
     } 
    } 
}