2016-04-12 46 views
2

Bir Java sunucu yazılımı değiştiriyorum. Tüm uygulama tek iş parçacığıdır. Değişikliklerimden biri çok zaman alıyor, bu yüzden asıl iş parçacığını dondurmamak için bunu senkronize olmayan bir şekilde yapmaya karar verdim.Java iş parçacığı güvenli kilitleme

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

Şu anda bu benim kodu (yorumlarına bakabilirsiniz) 'dir:

Bu

orijinal kod (değil gerçek kod, sadece bir örnektir) bir örnektir

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

neler Ben aslında şu gibi bir şey arıyorum:

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

Soru: Su en iyi uygulama nedir ch Java'da bir kilit mi? Örneğin, bir AtomicInteger ile kendim yapmalı mıyım?

Düzenleme: Geçerli uygulamam için yanıtıma bakın.

+1

çok net değil. operasyonların sırası nedir? İlk önce 'Packet.setData' ve' Packet.sendPacket' olarak adlandırıyorum mı? paket gönderildiğinde ne yapmak istersin? –

+0

Genel olarak kendi kilitleme kodunuzu uygulamaktan kaçınacağım: kesinlikle gerekmedikçe tekerleği yeniden icat etmeyiniz. Ve ReentrantLock gibi şeyler var. ve diğer birçok kişi tarafından kullanılır. "Kendini" yapmak her zaman yanlış anlama riskini taşır. – GhostCat

+0

Kilitlemeniz, gönderilirken bir pakete yazamayacağınız ve yazarken (farklı mesaj dizilerinden) gönderemeyeceğiniz anlamına gelir. Bu en iyi seçenek gibi görünmüyor. 'ClientConnection' için bir havuz uygulamanız gerekir, böylece birçok paket aynı anda aktarılabilir. – OldCurmudgeon

cevap

2

Verilerinizin bir kopyasını oluşturabilir ve eşzamanlılığı önlemek için kopyayı gönderebilirsiniz. CopyOnWriteArrayList kullanma

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

(daha sık sendPacket den setData çağıran olacağım vermiş olduğu) ayrıca eşzamanlılık önler ama verimli değildir kod körük ile paralellik gösterir:

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

Evet, bu işe yarayacaktı, ancak ana iş parçacığı üzerinde bir kopya yapmaktan kaçınmak istiyorum. – stonar96

+0

@AntonK., Ne demek istiyorsun? "Tüm uygulama tek iş parçacıklı." – ericbn

+0

Üzgünüm hatam ... –

1

en basit kilitlemek Reentrant Lock kullanabiliyor, bu durumda, eğer zaten sahip olduğunuzda kilidi almayı denerseniz, işlem başarılı olacaktır. Kodunuzda

, başarmak için diş Çocuğunuzun diş kilidi satın aldı kadar da ana iş parçacığı engellemek için wait() ve notify() kullanmak zorunda olacak, arzu:

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

Ayrıca bir ExecutorService kullanmayı düşünün ve ham Thread nesneleri oluşturulmuyor.

+0

Sanırım "setData" içindeki kilidi kaçırdınız, değil mi? Ama işlevsel olarak aynı değildir. – stonar96

+0

Bazı kopyalama-yapıştırma sorunları oldu, güncellendim. – Danikov

+0

Tamam, ancak ilk kod hala ikinci kodla aynı şekilde aynı değil. İkinci kodda, ana-thread 'sendPacket'i çağırdıktan sonra' setData' 've paket gönderilmeden önce çağrılabilir, bu aslında benim problemim. İlk kod bu sorunu çözecektir. – stonar96

0

Java'da böyle bir kilidin en iyi uygulaması nedir? Örneğin, bir AtomicInteger ile kendim yapmalı mıyım?

Sanırım ericbn'in yanıtı işe yarayacak. Ana iş parçacığını kullanarak ancak yine de Packet'un içeriğinin bir kopyasını kapmak iyi bir şeydir.

Ancak 1k arabelleği konusunda endişeli misiniz? Buradaki gerçek masrafının ana iş parçacığı kopyasının bir kopyasını oluşturmuyor olması, dizgesini her açışınızda paketini gönderiyorsunuz. Bu, nesne oluşturmaya kıyasla çok pahalı. Bir iş parçacığı havuzu kullanır ve paket işlerini ona gönderirdim.

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

Sen ağ üzerinden veri gönderen bu yüzden ekstra nesne bant genişliği ağ lag göre bir şey değildir.

+0

Veri dizisinin bir kopyasıyla çalışır. Ancak gerçek veri dizisi çok daha büyük ve paket çok sık gönderiliyor. Bu yüzden diziyi kopyalamaktan gerçekten vazgeçmek istiyorum. Artık bir kilit uygulamasına sahip bir çözümüm var. Ana iş parçacığımın donduğunu biliyorum, ancak sadece nadir durumlarda (setData' nadiren çağrılır). – stonar96

+0

Mevcut uygulamamı yeni yanıt olarak gönderdim. – stonar96

0

Benim şu anki uygulama:

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

Kilidi: Burada soruyorsun

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