IO fühlt sich nicht viel mit Multi-Threading zusammen, aber NIO hat die Art und Weise, wie Threads auf Anwendungsebene verwendet werden, verändert und einige praktische Schwierigkeiten gelöst. AIO ist asynchrone IO und die vorherige Serie sind ebenfalls verwandt. Zum Lernen und Aufnehmen schrieb ich auch einen Artikel, um NIO und AIO vorzustellen.
1. Was ist nio
NIO ist die Abkürzung neuer E/A, die der alten Stream-basierten E/A-Methode entgegengesetzt ist. Nach dem Namen zu urteilen, stellt es einen neuen Satz Java -E/A -Standards dar. Es wurde in Java 1.4 in den JDK aufgenommen und hat die folgenden Funktionen:
Alle Lesen und Schreibvorgänge aus dem Kanal müssen einen Puffer durchgehen, und der Kanal ist die Abstraktion von IO, und das andere Ende des Kanals ist die manipulierte Datei.
2. Puffer
Implementierung von Puffer in Java. Grundlegende Datentypen haben ihre entsprechenden Puffer
Einfache Beispiele für die Verwendung von Puffer:
Pakettest; Import Java.io.file; Import Java.io.FileInputStream; Import Java.nio.ByTebuffer; Import Java.nio.Channels.Filechannel; public class test {public static void main (String [] args) löst eine Ausnahme aus {FileInputStream fin = new FileInputStream (neue Datei ("d: //temp_buffer.tmp"); Filechannel fc = fin.getCanannel (); Bytebuffer bytebuffer = bytebuffer.alcode (1024); fc.read (bytebuffer); fc.close (); bytebuffer.flip (); // reversion lesen und schreiben}}Die zu verwendenden Schritte werden zusammengefasst:
1. Holen Sie sich Kanal
2. Beantragen Sie einen Puffer
3. Geben Sie eine Lese-/Schreibbeziehung zwischen Kanal und Puffer auf
4. Schließen
Das folgende Beispiel ist die Verwendung von NIO zum Kopieren von Dateien:
public static void niocopyfile (String Resource, String Ziel) löst IOException {FileInputStream fis = new FileInputStream (Ressource) aus; FileOutputStream fos = new FileOutputStream (Ziel); Filechannel ReadChannel = fis.getchannel (); // Dateikanal filechannel writeChannel = fos.getchannel (); // Dateikanal ByteBuffer Buffer = bytebuffer.alcode (1024); // im Datencache (true) {buffer.clear () lesen; int len = ReadChannel.read (Puffer); // in den Daten lesen if (len == -1) {break; // im} buffer.flip () lesen; WriteChannel.Write (Puffer); // in die Datei schreiben} ReadChannel.close (); WriteChannel.CLOSE (); }Es gibt 3 wichtige Parameter im Puffer: Position, Kapazität und Grenze
Hier müssen wir zwischen Kapazität und Obergrenze unterscheiden. Wenn beispielsweise ein Puffer 10 KB hat, ist 10 KB die Kapazität. Wenn ich die 5 -KB -Datei in den Puffer lese, beträgt die Obergrenze 5 KB.
Hier ist ein Beispiel, um diese 3 wichtigen Parameter zu verstehen:
public static void main (String [] args) löst eine Ausnahme aus {bytebuffer b = bytebuffer.alcode (15); // 15-byte Buffer System.out.println ("limit =" + b.limit () + "CAPACAY =" + b.Capacity () + "Position =" + b.position ()); für (int i = 0; i <10; i ++) {// 10 Bytes von Daten speichern b.put ((byte) i); } System.out.println ("limit =" + b.limit () + "CAPAPY =" + b.Capacity () + "Position =" + B.Position ()); B.flip (); // PositionSystem zurücksetzen. für (int i = 0; i <5; i ++) {System.out.print (b.get ()); } System.out.println (); System.out.println ("limit =" + b.limit () + "CAPAPY =" + b.Capacity () + "Position =" + B.Position ()); B.flip (); System.out.println ("limit =" + b.limit () + "CAPAPY =" + b.Capacity () + "Position =" + B.Position ()); }Der gesamte Prozess ist in der Abbildung dargestellt:
Zu diesem Zeitpunkt liegt die Position von 0 bis 10 und die Kapazität und die Grenze bleiben unverändert.
Dieser Vorgang setzt die Position zurück. Wenn Sie den Puffer vom Schreibmodus in den Lesemodus konvertieren, müssen Sie diese Methode normalerweise durchführen. Der Flip () -Operationsvorgang setzt nicht nur die aktuelle Position auf 0 zurück, sondern legt auch die Grenze auf die Position der aktuellen Position fest.
Die Bedeutung der Grenze besteht darin, zu bestimmen, welche Daten aussagekräftig sind. Mit anderen Worten, die Daten von Position zu Begrenzung sind aussagekräftige Daten, da sie die Daten aus dem letzten Vorgang sind. Daher bedeuten Flip -Operationen häufig das Lesen und Schreiben von Conversion.
Die gleiche Bedeutung wie oben.
Die meisten Methoden in Puffer ändern diese 3 Parameter, um bestimmte Funktionen zu erzielen:
öffentlicher Final Puffer REWIND ()
Setzen Sie die Position Null und klare Marke
öffentlicher Final Puffer Clear ()
Stellen Sie die Position Null ein und setzen Sie die Grenze auf die Kapazitätsgröße und löschen
öffentlicher Final Puffer Flip ()
Setzen Sie zuerst die Grenze für die Position fest, an der sich die Position befindet, und setzen
Datei, die dem Speicher zugeordnet sind
public static void main (String [] args) löst Ausnahme aus {randomAccessfile raf = new randomAccessfile ("c: //mapfil.txt", "rw"); Filechannel fc = raf.getchannel (); // Die Datei in den Speicher abgebildetem Bytebuffer mbb = fc.map (filechannel.mapMode.read_write, 0, raf.length ()); while (mbb.hasremaining ()) {System.out.print ((char) mbb.get ()); } mbb.put (0, (byte) 98); // Die Datei raf.close () ändern; }Das Ändern des kartierten Bytebuffer entspricht der Änderung der Datei selbst, sodass die Betriebsgeschwindigkeit sehr schnell ist.
3. Kanal
Allgemeine Struktur des Multi-Thread-Netzwerkservers:
Einfacher Multi-Thread-Server:
public static void main (String [] args) löst eine Ausnahme aus {ServerSocket EchoServer = null; Socket Clientocket = null; try {echoServer = new serversocket (8000); } catch (ioException e) {System.out.println (e); } while (true) {try {Clientsocket = echoServer.accept (); System.out.println (Clientsocket.getRemotesocketadDress () + "Connect!"); tp.execute (neue Handlemsg (ClientSocket)); } catch (ioException e) {System.out.println (e); }}}Die Funktion besteht darin, die Daten an den Client zurückzuschreiben, wenn der Server liest.
Hier ist TP ein Thread -Pool, und Handlemsg ist die Klasse, die Nachrichten übernimmt.
statische Klasse Handlemsg implementiert runnable {Teil des Informations -Public void run () {try {is = new buferedReader (neuer InputStreamReader (Clientsocket.getInputStream ()); OS = New Printwriter (ClientSocket.GetOutputStream (), True); // Lesen Sie die vom Client gesendeten Daten aus der inputStream String inputline = null; langes b = system.currentTimemillis (); while ((inputline = is.readline ())! = null) {os.println (inputline); } langes e = System. CurrentTimemillis (); System. out.println ("Ausgeben:"+(e - b)+"ms"); } catch (ioException e) {e.printstacktrace (); } endlich {Ressource schließen}}}Kunde:
public static void main (String [] args) löst eine Ausnahme aus {Socket Client = null; Printwriter writer = null; BufferedReader reader = null; try {client = new Socket (); Client.Connect (New Inetsocketaddress ("Localhost", 8000); writer = neuer printwriter (client.getOutputStream (), true); writer.println ("Hallo!"); writer.flush (); reader = new bufferedReader (neuer InputStreamReader (client.getInputStream ())); System.out.println ("From Server:" + reader.readline ()); } catch (Ausnahme e) {} endlich {// Ressourcenschließe auslassen}}Die obige Netzwerkprogrammierung ist sehr einfach, und es wird einige Probleme mit dieser Methode geben:
Verwenden Sie für jeden Client einen Thread. Wenn der Kunde eine Ausnahme wie Verzögerung erlebt, kann der Thread lange Zeit besetzt sein. Weil die Datenvorbereitung und das Lesen in diesem Thread enthalten sind. Wenn es viele Kunden gibt, kann es zu diesem Zeitpunkt viele Systemressourcen konsumieren.
Lösung:
Verwenden Sie nicht blockierende NIO (lesen Sie die Daten, ohne zu warten, die Daten sind vor der Arbeit bereit).
Um die Effizienz des NIO -Gebrauchs widerzuspiegeln.
Hier simulieren wir zuerst einen ineffizienten Client, um die Verzögerung aufgrund des Netzwerks zu simulieren:
private statische Executorservice TP = Executors.NewCachedThreadpool (); private statische endgültige int sleep_time = 1000*1000*1000; öffentliche statische Klasse Echoclient Implements Runnable {public void run () {try {client = new socket (); Client.Connect (New Inetsocketaddress ("Localhost", 8000); writer = neuer printwriter (client.getOutputStream (), true); writer.print ("h"); Locksupport.parknanos (sleep_time); writer.print ("e"); Locksupport.parknanos (sleep_time); writer.print ("l"); Locksupport.parknanos (sleep_time); writer.print ("l"); Locksupport.parknanos (sleep_time); writer.print ("o"); Locksupport.parknanos (sleep_time); writer.print ("!"); Locksupport.parknanos (sleep_time); writer.println (); writer.flush (); } catch (Ausnahme e) {}}}Serverseitige Ausgabe:
Ausgeben: 6000 ms
Ausgeben: 6000 ms
Ausgeben: 6000 ms
Ausgeben: 6001 ms
ausgeben: 6002 ms
ausgeben: 6002 ms
ausgeben: 6002 ms
ausgeben: 6002 ms
Ausgeben: 6003 ms
Ausgeben: 6003 ms
Weil
while ((inputline = is.readline ())! = null)
Es ist blockiert, also wird die Zeit damit verbracht, zu warten.
Was würde passieren, wenn NIO verwendet würde, um mit diesem Problem umzugehen?
Eine der großen Merkmale von NIO ist: Benachrichtigen Sie mich, wenn die Daten fertig sind
Der Kanal ähnelt ein bisschen wie Streams, und ein Kanal kann Dateien oder Netzwerkhöhlen entsprechen.
Der Selektor ist ein Selektor, der einen Kanal auswählen und etwas tun kann.
Ein Faden kann einem Selektor entsprechen, und ein Selektor kann mehrere Kanäle abfragen, und jeder Kanal verfügt über einen Sockel.
Im Vergleich zu dem obigen Faden, der einer Steckdose entspricht, kann nach der Verwendung von NIO ein Faden mehrere Steckdosen abfragen.
Wenn der Selektor select () aufruft, wird prüfen, ob ein Client die Daten erstellt hat. Wenn keine Daten fertig sind, blockiert Select (). Es wird normalerweise gesagt, dass NIO nicht blockiert ist, aber wenn die Daten nicht fertig sind, wird es immer noch blockiert.
Wenn Daten fertig sind, wird nach dem Aufrufen von Select () ein SelectionKey zurückgegeben. Der SelectionKey gibt an, dass die Daten eines Kanals auf einem Selektor erstellt wurden.
Dieser Kanal wird nur ausgewählt, wenn die Daten fertig sind.
Auf diese Weise implementiert NIO einen Thread, um mehrere Clients zu überwachen.
Der Client mit der gerade simulierten Netzwerkverzögerung wirkt sich nicht auf die Threads unter NIO aus, da die Daten, wenn ein Socket -Netzwerk verzögert, nicht fertig ist und der Selektor es nicht auswählt, sondern andere vorbereitete Clients auswählt.
Der Unterschied zwischen SelectNow () und Select () besteht darin, dass SelectNow () nicht blockiert. Wenn kein Client Daten vorbereitet, blockiert SelectNow () und gibt 0 zurück. Wenn ein Client Daten vorbereitet, gibt SelectNow () die Anzahl der vorbereiteten Clients zurück.
Hauptcode:
Pakettest; importieren java.net.inetaddress; import java.net.inetsocketaddress; import Java.net.socket; import Java.nio.Channels.SelectionKey; Import Java.nio.Channels.Seselector; java.nio.channels.socketchannel; import java.nio.channels.spi.abstractSelector; import Java.nio.channels.spi.SelectorProvider; java.util.concurrent.executorservice; import Java.util.concurrent.executors; public class multithreadnioechoserver {public static map <Socket, long> Geym_Time_stat = new Hashmap <Socket, Long> (); Klasse Echoclient {private linkedList <ByteBuffer> outq; Echoclient () {outq = new LinkedList <ByteBuffer> (); } public linkedList <ByteBuffer> getOutputqueue () {return outq; } public void enqueue (bytebuffer bb) {outq.addfirst (BB); }} class Handlemsg implementiert runnable {SelectionKey SK; ByteBuffer BB; public Handlemsg (SelectionKey SK, ByteBuffer BB) {Super (); this.sk = sk; this.bb = bb; } @Override public void run () {// Todo automatisch generierte Methode Stub Echoclient echoclient = (echoclient) sk.attachment (); echoclient.enqueue (BB); sk.interestops (selectionKey.op_read | selectionKey.op_write); Selector.WakeUp (); }} Private Selector Selector; private ExecutorService TP = Executors.NewCachedThreadpool (); private void startserver () löst Ausnahme aus {selector = selectorProvider.provider (). openSelector (); ServerSocketChannel SSC = ServerSocketchannel.open (); SSC.ConfigureBlocking (falsch); Inetsocketaddress isa = new inetsocketaddress (8000); Ssc.Socket (). Bind (ISA); // Registrieren Sie das Interesse Ereignis, hier interessiert sich an der AccPet Event SelectionKey AcceptKey = SSC.register (Selector, SelectionKey.op_accept); für (;;) {selector.Select (); Set readykeys = selector.selectedKeys (); Iterator I = Readykeys.Iderator (); lang E = 0; while (i.hasnext ()) {SelectionKey SK = (SelectionKey) I.Next (); I.Remove (); if (sk.isacceptable ()) {doaccept (sk); } else if (sk.isvalid () && sk.isreadable ()) {if (! Geym_time_stat.ContainsKey ((Socketchannel) sk .Channel (). Socket ())) {Geym_time_stat.put (((Sockethannel)). } doread (SK); } else if (sk.isvalid () && sk.iswrible ()) {dowrite (sk); e = system.currentTimemillis (); lang B = Geym_Time_stat.remove (((Socketchannel) sk .Channel ()). Socket ()); System.out.println ("Ausgeben:" + (e - b) + "ms"); }}}} private void dowrite (SelectionKey SK) {// Todo automatisch generierte Methode Stub Socketchannel Channel = (Socketchannel) sk.channel (); Echoclient echoclient = (echoclient) sk.attachment (); LinkedList <ByteBuffer> outq = echoclient.getOutputqueue (); Bytebuffer bb = outq.getLast (); try {int len = Channel.Write (BB); if (len == -1) {disconnect (SK); zurückkehren; } if (bb.remaint () == 0) {outq.removelast (); }} catch (Ausnahme e) {// toDo: Behandeln Sie die Ausnahme -Trennung (SK); } if (outq.size () == 0) {sk.interestops (selectionKey.op_read); }} private void doread (SelectionKey SK) {// Todo automatisch generierte Methode Stub Socketchannel Channel = (Socketchannel) sk.channel (); Bytebuffer bb = bytebuffer.alcode (8192); Int len; try {len = Channel.read (BB); if (len <0) {trenconnect (SK); zurückkehren; }} catch (Ausnahme e) {// toDo: Behandeln Sie die Ausnahme -Trennung (SK); zurückkehren; } bb.flip (); tp.execute (neue Handlemsg (SK, BB)); } private void disconnect (selectionKey sk) {// todo automatisch generierte Methode Stub // Der Trockenschließvorgang weglassen} private void Doaccept (SelectionKey SK) {// Todo auto-generierter Methode StubserversocketAntel Server = (ServerSocketAntel) sk.channel (); Socketchannel ClientChannel; try {clientChannel = server.accept (); ClientChannel.ConfigureBlocking (falsch); SelectionKey ClientKey = ClientChannel.register (Selector, SelectionKey.op_read); Echoclient echoclinet = new echoclient (); ClientKey.attach (Echoclinet); Inetaddress clientAddress = clientChannel.socket (). GetInetaddress (); System.out.println ("Akzeptierte Verbindung von" + ClientAddress.GethostadDress ()); } catch (Ausnahme e) {// Todo: Handle-Ausnahme}} public static void main (String [] args) {// Todo automatisch generierter Methode Stub MultitHREADnioEchoServer EchoServer = new multithreadnioechoServer (); try {echoServer.startServer (); } catch (Ausnahme e) {// toDo: Handle -Ausnahme}}}Der Code dient nur als Referenz, und seine Hauptfunktion ist, dass Sie an verschiedenen Ereignissen interessiert sind, um unterschiedliche Dinge zu tun.
Bei der Verwendung des zuvor simulierten verzögerten Clients liegt der Zeitverbrauch in diesem Zeitpunkt zwischen 2 ms und 11 ms. Die Leistungsverbesserung ist offensichtlich.
Zusammenfassen:
1. Nachdem NIO die Daten vorbereitet hat, wird sie der Verarbeitung an die Anwendung übergeben. Der Datenlesen-/Schreibvorgang ist im Anwendungs -Thread weiterhin abgeschlossen und wird nur die Wartezeit in einen separaten Thread abziehen.
2. Datenvorbereitungszeit speichern (da der Selektor wiederverwendet werden kann)
5. AIO
Merkmale von AIO:
1. Benachrichtigen Sie mich nach dem Lesen
2. IO wird nicht beschleunigt, sondern nach dem Lesen benachrichtigt
3.. Verwenden Sie Rückruffunktionen, um die Geschäftsabwicklung durchzuführen
AIO -verwandter Code:
AsynchronousServersocketchannel
server = asynchronousServerSocketchannel.open (). Bind (New InetSocketaddress (Port));
Verwenden Sie die Akzeptanzmethode auf dem Server
öffentliches Zusammenfassung <a> void Accept (ein Anhang, CompletionHandler <asynchronousSocketchannel, "Super a> Handler);
CompletionHandler ist eine Rückrufschnittstelle. Wenn ein Kunde akzeptiert wird, macht es das, was im Handler ist.
Beispielcode:
server.accept (null, neuer CompletionHandler <AsynchronousSocketchannel, Object> () {endgültiger Bytebuffer Buffer = Bytebuffer.alcode (1024); {puffer.clear (); server.accept (null, this);Hier nutzen wir die Zukunft, um sofortige Rendite zu erzielen. Für die Zukunft finden Sie im vorherigen Artikel
Basierend auf dem Verständnis von NIO und dem Betrachten von AIO besteht der Unterschied darin, dass AIO auf den Lese- und Schreibprozess wartet, um die Rückruffunktion vor dem Abschluss aufzurufen.
NIO ist synchron und nicht blockiert
AIO ist asynchron und nicht blockiert
Da der NIO -Lese- und Schreibvorgang im Anwendungs -Thread noch abgeschlossen ist, ist NIO nicht für Personen mit langem Lese- und Schreibvorgang geeignet.
Der AIO-Lese- und Schreibprozess wird erst nach Abschluss der Fertigstellung benachrichtigt, sodass AIO für Aufgaben des Schwergewichts und langfristigen Les- und Schreibprozesses kompetent ist.