Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Java 7 - JSR 203 - NIO2

Java 7 - JSR 203 - NIO2
Java 7 
JSR 203 - "NIO2"
(Erweiterung der I/O Funktionalität)
 

Java Magazin, Dezember 2011
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

Mit diesem Artikel geht die Reihe über Neuerungen in Java 7 weiter.  Diesmal wollen wir uns die I/O Erweiterungen (NIO2), die mit Java 7 gekommen sind, genauer ansehen.

Bisher gibt es im JDK zwei Top-Level-Packages mit I/O Funktionalität.  Dies ist zum einen das Package java.io , das es schon seit dem JDK 1.0 gibt.  Mit Java 1.4 ist dann das Package java.nio neu dazugekommen.  Die darin enthalte Funktionalität nennt sich NIO (= new i/o).

Mit Java 7 ist nun wieder neue I/O Funktionalität in den JDK gekommen.  Diese Erweiterung nennt sich NIO2 und findet auch unter  dem Top-Level-Package java.nio ihren Platz.

Die zwei großen NIO2 Themen sind:

  • neues File System API und
  • Erweiterungen der asynchronen I/O.
Daneben haben die Entwickler bei Sun bzw. Oracle noch ein paar kleinere I/O-Themen in NIO2 untergebracht.  Es geht dabei im Wesentlichen um die Unterstützung neuer Protokolle bzw. Protokollversionen.  Konkret sind dies:
  • SDP . Java 7 unterstützt das Socket Direct Protocol  (SDP, siehe / SDP /)  unter Solaris und Linux.  Für den Java Programmierer gibt es dabei kein neues API.  SDP kann vielmehr über die bisherigen Network-APIs wie Socket , ServerSocket , SocketChannel , ServerSocketChannel , usw.  nach entsprechender Konfiguration des Systems (siehe / SDPCNF /) genutzt werden.
  • TLS . Java 7 unterstützt über bestehende APIs den Transport Layer Security (TLS) Standard in der neuen Version 1.2.
  • IPv6 . Des Weiteren unterstützt Java 7 die Nutzung des neuen mit Vista eingeführten, IPv6-fähigen Protokollstacks unter Windows.  Auch diese Funktionalität wird über bestehende APIs verfügbar gemacht.
Schauen wir uns nun die beiden großen Themen neues File System API und Erweiterungen der asynchronen I/O im Detail an.

Neues File System API

Ausgangssituation

Wer bisher in Java auf das File System zugreifen wollte, kam um die Benutzung der Klasse java.io.File nicht herum.  Die Klasse gibt es seit Java 1.0 im JDK.  Leider hat sich gezeigt, dass sie einige Schwächen hat.  Kritik gibt es dabei im Wesentlichen an zwei Punkten:  Zum einen ist das API Design an einigen Stellen suboptimal.  Zum anderen ist der API Umfang zum Teil nicht ausreichend.

Ein Beispiel für die Designschwächen des API ist die Instanzmethode delete() , mit der man eine Datei / ein Directory von einem Java Programm aus löschen kann:

  public boolean delete()

Der Returnwert gibt an, ob die Datei / das Directory, mit dem das Objekt assoziiert ist, wirklich gelöscht werden konnte oder nicht.  Das Problem ist nun, dass im Fehlerfall (Returnwert = false ) unklar ist, warum das Löschen nicht funktioniert hat,  denn weitere Indikatoren über die Fehlerursache gibt es nicht:  die Methode wirft keine Exception.  Das heißt, man kann in einer solchen Situation weder vom Programm aus auf den Fehler reagieren noch an den Benutzer eine sinnvolle Fehlermeldung ausgeben.  delete() ist nicht die einzige Methode, die dieses Problem hat; für mkdir() , mkdirs() , renameTo() , setLastModified() , setReadOnly() gilt im Prinzip das gleiche.

Die Kritik am unzureichenden Umfang des bestehenden APIs lässt sich am besten am API für den Zugriff auf Dateiattribute festmachen.  Die Klasse File stellt ein paar Methoden dafür zur Verfügung.  Zum Beispiel lässt sich mit lastModified() , setLastModified() der Zeitpunkt der letzten Modifikation lesen bzw. schreiben.  Ein API, mit dem man aber auf alle Attribute einer Datei zugreifen kann, gibt es nicht.  Ein weiterer Nachteil ist, dass das existierende API auch noch relativ ineffizient ist, wenn es um den Zugriff auf mehre Attribute einer Datei geht.  Jedes Attribut muss mit jeweils einem Methodenaufruf bearbeitet werden.  Bulk -Zugriffe, die mehrere oder alle Attribute mit einem Methodenaufruf lesen oder schreiben, gibt es nicht.
 

java.nio.file.Path und java.nio.file.Files

Das neue File System API findet sich unter im Package java.nio.file .  Zwei wichtige Abstraktionen sind das Interface java.io.file.Path und die Klasse java.io.file.Files .  Zusammen  ersetzen sie die komplette Funktionalität der Klasse java.io.File , ohne dass die Klasse File in Zukunft deprecated wird.

Schauen wir uns also die beiden neuen Abstraktionen mal genauer an, mit besonderem Schwerpunkt auf den oben diskutierten Kritikpunkten an File .

Path repräsentiert ein Element im Filesystem, das über einen hierarchischen Pfad identifiziert wird,  also eine Datei oder ein Directory in Windows, UNIX, Linux, usw. .  Files ist eine Klasse mit statischen Methoden, die Operationen zur Verfügung stellen, um Dateien und Directories zu bearbeiten: copy() , move() , delete() , ... .  Das bedeutet, die Funktionalität die bisher von java.io.File und seinen Instanzmethoden zur Verfügung gestellt wurde, findet man nun in java.nio.files.Files' statischen Methoden.  Die Dateien bzw. Directories, die bearbeitet werden sollen, werden als Objekte vom Typ Path an Files ' statischen Methoden übergeben.

Schauen wir uns die delete() Methode von Files an.  Sie hat folgende Signatur:

 public static void delete(Path path) throws IOException;

Der Parameter path spezifiziert die Datei / das Directory das gelöscht werden soll.  Im Vergleich zur bisherigen delete() Methode von File hat die neue Methode in Files keinen expliziten Returnwert mehr.  Dafür wirft sie nun eine IOException .  Diese Exception ist zum einen die Indikation dafür, dass ein Fehler aufgetreten ist.  Andererseits spezifiziert sie auch die Ursache des Fehlers.  Man sieht also, an dieser Stelle ist das API Design verbessert worden: es ist jetzt möglich, den konkreten Fehler  im Programm auszuwerten und auf ihn zu reagieren.

Wir wollen uns hier jetzt nicht jede Methode von Files ansehen.  Sie lassen sich eigentlich alle ganz gut mit Hilfe der Javadoc verstehen.  Schauen wir uns deshalb nur noch an, wie der Zugriff auf Dateiattribute im neuen File System API gelöst ist.

Files stellt dafür die Zugriffmethoden getAttribute() und setAttribute() zur Verfügung.  Das Attribut, auf das hierbei zugegriffen werden soll, wird durch einen speziellen String spezifiziert, der als Parameter der jeweiligen Methode übergeben wird.  Zum Beispiel ist "basic:lastModifiedTime" der String, der den Zeitpunkt der letzten Modifikation spezifiziert.  Die Strings selbst sind zu Gruppen in so genannten Attribute Views organisiert: BasicFileAttributeView , DosFileAttributeView , PosixFileAttributeView im Package java.nio.file.attribute .

Unser Beispielstring "basic:lastModifiedTime" ist aus BasicFileAttributeView .  Wir benutzen ihn nun, um den Zeitpunkt der letzten Modifikation der mit myFile vom Typ Path assoziierten Datei zu lesen:

  FileTime ft = (FileTime) Files.getAttribute(myfile, "basic:lastModifiedTime");

Genau genommen könnten wir in diesem Beispiel auch "lastModifiedTime" statt "basic:lastModifiedTime" verwenden, das Strings ohne View-Prefix als Attribute aus dem BasicFileAttributeView interpretiert werden.

Schauen wir uns nun an, wie das Lesen bzw. Schreiben mehrerer Attribute einer Datei (also Bulk-Zugriffe) funktionieren.  Schreibende Bulk-Zugriffe gibt es nicht.  Aber für das Lesen von mehreren Attributen bietet Path die Methode readAttributes() an.  Hier muss man alle Attribute, die man lesen will, in einem String Parameter spezifizieren.  Das heißt, der Aufruf:

  Map<String, ?> result = Files.getAttribute(myfile, "basic:size,lastModifiedTime");

ließt nun zwei BasicFileAttribute s, nämlich die Größe und den Zeitpunkt der letzten Modifikation der Datei.  Der Returnwert ist eine Map , die den Namen des Attributs auf seinen gelesenen Wert abbildet.

Wie oben bereits erwähnt kann man den View-Prefix im Fall von Attributen aus dem BasicFileAttributeView weglassen.  Der Aufruf:

 Map<String, ?> result = Files.getAttribute(myfile, "*");

liefert die Werte für alle Attribute der BasicFileAttributeView in der result Map.

Reichen die vorgegeben Views und ihre Attribute nicht aus, weil man auf exotische Attribute (z.B. MP3-Id-Tags von MP3-Audiodateien) zugreifen möchte, so kann man durch Implementieren des Interfaces UserDefinedFileAttributeView einen  neuen Attribute-View mit eigenen Attributen definieren.  Natürlich muss man dabei den Zugriff auf die neuen Attribute komplett selbst implementieren.  Dafür kann der Zugriff auf die Attribute dann aber über Files Methoden erfolgen.

Wenn man nun sieht, was man alles mit Files machen kann, das früher mit File nur schlecht oder gar nicht möglich war, kommt vielleicht der Wunsch auf, in bereits bestehendem Code bei zukünftigen Erweiterungen Files statt File zu verwenden.  Dazu muss man glücklicherweise nicht den existierenden Code, der auf Basis von File implementiert ist, unter Verwendung von Files und Path reimplementieren.  Vielmehr hat File in Java 7 eine neue Methode toPath() , die es erlaubt, das File Objekt in ein Path Objekt zu konvertieren und so ist es problemlos möglich die neue Funktionalität von Files zu nutzen.  So kann man dann den bisherigen Aufruf

  boolean isDelteted = myFile.delete();

einfach zu

 try { Files.delete(myFile.toPath()); }
 catch(IOException ioe) { /* TODO: handle ioe */ }

ändern, wenn man auf den Fehler beim Löschen einer Datei reagieren will.
 

Weitere File System API Neuerungen

Files und Path sind aber nicht die einzigen Neuerungen im NIO 2 File System API.

Neu sind auch die Klassen FileSystem , FileSystems , FileStore (alle aus dem Package java.nio.file ), die das API für den Zugriff auf bestimmte Elemente des Dateisystems bilden.

  • FileSystem : FileSystem stellt, wie der Name schon sagt, die Funktionalität zum Zugriff auf das Dateisystem zur Verfügung. FileSystem enthält zum Beispiel die Methode getPath() , mit der man einen String, der einen Pfadnamen (zum Beispiel "/tmp/work/myWork.txt" ) enthält, in ein Path Objekt konvertieren kann.
  • FileSystems : Wie kommt man aber an das FileSystem Objekt, auf dem man dann getPath() aufrufen kann?  FileSystems (diesmal Plural!) bietet hier statische Factory Methoden an, mit denen man FileSystem Objekte erzeugen kann.  Im Allgemeinen wird man wohl:
      FileSystem myFs = FileSystems.getDefault();

    aufrufen, um das Dateisystem zu bekommen, auf dem das Programm aktuell ausgeführt wird.

  • FileStore : FileSystem (Singular!) enthält auch die Methode
 public Iterable<FileStore> getFileStores();

um zu den FileStore Objekten des Dateisystems zu navigieren.  Was sind die FileStores ?  Das sind die Abstraktionen, die die betriebssystemspezifischen Unterelemente des Dateisystems, also so was wie Partitions, Volumes, Devices, ... repräsentieren.  Unter Windows sind das die mit Buchstaben bezeichneten Laufwerke: C:, D:, ... .

Neben dem Zugriffs-API bringt die NIO2 noch zwei wichtige Neuerungen beim File System API: Watch Service und System Provider Interface .

Wenn man vor Java 7 einen View auf ein Directory am Bildschirm anzeigen wollte, der automatisch die Änderungen (z.B. Löschen von Dateien im Directory) anzeigt, so musste man im Anwendungsprogramm das Directory periodisch pollen.  Typischerweise machte man das, indem man auf dem java.io.File Objekt des Directorys zeitgesteuert immer wieder eine der list() oder listFile() Methoden aufrief.

Mit NIO 2 gibt es nun den java.nio.file.WatchService .  Dieser liefert, wenn entsprechend konfiguriert, die Änderungen als Events, die man aktiv mit poll() oder take() abholen muss.  Dabei haben beide Methoden die gleiche Semantik wie bei der Queue: poll() liefert null zurück, wenn kein Event da ist, take() blockiert und wartet, bis ein Event da ist.
Die Implementierung des WatchService ist natürlich systemabhängig.  Nur bei Betriebssystemen, die selbst einen Notifizierungsmechanismus für Dateisystemänderungen zur Verfügung stellen, kann der WatchService darauf aufsetzten.  Wenn es so etwas nicht im Betriebssystem gibt, übernimmt der WatchService das Pollen, das man früher selbst machen musste, um daraus die entsprechenden Events zu machen.

Nachdem man all die tollen Features des neuen File System API gesehen hat, kann man auf die Idee kommen, dass man einen eher exotischen Dateienspeicher (z.B. ein WORM Archiv) in Java über das neue File System API ansprechen möchte.  Dazu muss man das System Provider Interface implementieren.
 

Asynchrone I/O Erweiterungen

Synchrone und asynchrone I/O in Java

Synchrone I/O ist die einfache Form der I/O.  Sie gibt es in Java seit 1.0.  Bei der synchronen I/O  wartet die Leseoperation 1 , bis Daten vorhanden sind.  Sie liest diese dann ein und kehrt mit dem Kontrollfluss und den Daten zum Anwendungsprogramm zurück.  Das Problem mit synchroner I/O ist, dass die Leseoperation wartet, solange keine Input-Daten vorhanden sind, und sie damit den Kontrollfluss anhält.  In Java bedeutet dies, dass der Thread, der die Leseoperation ausführt, so lange steht.  Das ist nicht grundsätzlich ein Problem.  Da Multithread-Programmierung in Java gut unterstützt wird, kann man (falls nötig) weitere Threads in der JVM starten, um die anstehenden Arbeiten parallel zu dem Thread auszuführen, der auf Input wartet.  1 Wir betrachten hier in der Diskussion nur die Input-Seite.  Die Output-Seite funktioniert im Prinzip genauso.  Bei der Output-Seite wartet man darauf, dass die Daten „abtransportiert“ werden.  Bei Socket I/O bedeutet es zum Beispiel, dass man, nachdem alle Zwischenpuffer voll gelaufen sind, darauf wartet, dass die Gegenseite die Daten endlich liest, so dass wieder weitere Daten auf den Socket geschrieben werden können.

Synchrone I/O kann aber zu einem Problem werden, zum Beispiel bei Servern, die mit einer hohen Anzahl von Clients (zigtausende!) parallel kommuniziert.  Hier brauch man dann nämlich für jede Clientverbindung einen Thread, der auf den Input vom Client wartet.  Auf einigen Systemen kann man so an die maximale Anzahl Threads stoßen, die in der JVM verfügbar sind.  Aber selbst, wenn das nicht der Fall ist, ist dies keine besonders effiziente Architektur, da man eine extrem hohe Anzahl von Threads benötigt und entsprechend Systemressourcen sowohl in der JVM als auch im Betriebssystem bindet.  Kommt noch dazu, dass die meisten dieser Threads dann nichts anderes machen, als im Idle-Zustand auf Input zu warten.

Das ist im Wesentlichen die Motivation, die dazu geführt hat, dass mit Java 1.4 asynchrone I/O im Rahmen der NIO eingeführt wurde.  Bei der asynchronen I/O wartet die Leseoperation nun nicht mehr, bis Input-Daten vorhanden sind, sondern kehrt, wenn keine da sind, sofort und ohne Daten zurück.  Dass die Leseoperation hier nicht blockiert, ist zwar wichtig, aber nicht das einzige Element, dass asynchrone I/O ausmacht.  Zusätzlich braucht man einen Notifikationsmechanismus, der darüber informiert, dass Input-Daten vorhanden sind und sich die Leseoperation überhaupt lohnt.  Für den Notifikationsmechanismus hat man sich bei der Java 1.4 Entwicklung vom select() Systemcall inspirieren lassen, der zuerst in 4.2 BSD implementiert wurde und später auch in ähnlicher Form in anderen Betriebssystemen Eingang gefunden hat.

Konkret sind mit Java 1.4 folgende Klassen für die asynchrone I/O dazu gekommen:

  • Selector , der den Notifikationsmechanismus zur Verfügung stellt,
  • SocketChannel (bzw. ServerSocketChannel ) als I/O Endpunkt für die asynchrone Kommunikation und
  • SelectionKey , der die Assoziation von Selector und SocketChannel repräsentiert.
Das heißt, mit Java 1.4 hat man drei neue Klassen für die asynchrone Kommunikation, wo man vorher mit einer Klasse ( Socket ) für die synchrone Kommunikation ausgekommen ist.  Es verdeutlicht, dass die Implementierung eines asynchronen Servers auf Basis dieser Abstraktionen nicht völlig trivial ist.  Im Prinzip und etwas verkürzt sieht eine solche Implementierung so aus:

   Selector selector = SelectorProvider.provider().openSelector();      // 1
   …
   while ( run ) {
      if ( selector.select() > 0 )  {                                   // 4
         Set readyKeys = selector.selectedKeys();
         Iterator i = readyKeys.iterator();

         while(i.hasNext()) {                                           // 8
            SelectionKey sk = (SelectionKey)i.next();
            i.remove();
            …

            if( sk.isReadable( ) ) {                                    //11
               ByteBuffer buf = ByteBuffer.allocate(BYTE_BUFFER_SIZE);
               SocketChannel sc = (SocketChannel) sk.channel();
               bytesRead = sc.read(buf);
               handleClientDataAsynchronously(sc, buf);                 // 15
            }
            …
         }
       }
       …
   }

Zuerst muss man sich ein Selector Objekt erzeugen (Zeile 1).  Im Aufruf seiner select() Methode wartet der Selector-Thread dann darauf, dass sich auf einem der bei ihm registrierten SocketChannel s etwas tut (Zeile 4).  Werden Daten auf einem oder mehreren SocketChannel s empfangen, für die man sich registriert hat, so kommt die select() Methode zurück.  Nun muss man über alle SelectionKey s iterieren (Zeile 8), denn jeder Key repräsentiert die Notifikation dafür, dass sich auf dem korrespondierenden SocketChannel etwas getan hat.  Mit dem Aufruf von isReadable() (Zeile 11) wird geprüft, ob Daten auf dem Channel angekommen sind.  Wenn ja, werden diese Daten in einen ByteBuffer eingelesen.  Mit dem Aufruf von handleClientDataAsynchronously() (Zeile 15) werden die Daten an einen anderen Thread zur Weiterverarbeitung übergeben.  Das hier ein Threadwechsel stattfindet, ist enorm wichtig, denn der Selector-Thread, der den select() bedient, darf nicht mit anderen Aufgaben aufgehalten werden.  Er muss vielmehr über die verbleibenden SelectionKey s iterieren und, wenn er damit fertig ist, wieder in den select() Aufruf zurückkehren.  Denn es kann sein, dass während der Iteration an weiteren SocketChannel s schon wieder Daten angekommen sind.  Dann muss der Selector-Thread diese wieder lesen und ausliefern.

So sieht das Programmiermodell mit der asynchronen I/O aus Java 1.4 also aus.  Wenn man genau hinschaut, fragt man sich, warum man den ganzen Code (der in der Praxis noch komplizierter ist, weil man sich nicht nur für Input interessiert) immer wieder neu in der Anwendung implementieren muss.  Schön wäre es doch, wenn der redundante Code im JDK wäre und man die Input-Daten in einem asynchronen Callback ans Anwendungsprogramm ausgeliefert bekäme.
 

Neuerungen der Asynchronen I/O in Java

 
Und genauso hat man es jetzt in der NIO2 gemacht.  Die Grundlage bildet die neue Klasse AsynchronousSocketChannel 2 .  Und so einfach geht asynchrone I/O nun damit: 2 Die Namenswahl ist recht passend: während der SocketChannel den Selector braucht, kann der AsynchronousSocketChannel asynchrone I/O von sich aus durchführen.

   AsynchronousSocketChannel asc = AsynchronousSocketChannel.open();       // 1

   … // connect

   final ByteBuffer buf = ByteBuffer.allocate(BYTE_BUFFER_SIZE);

   asc.read(buf, null, new CompletionHandler<Integer, Object> () {         // 4
                               public void failed(Throwable t, Object o) { … }
                               public void completed(Integer num, Object o) {
                                           … // handle the num bytes read into buf
                               }
                             });

Nach Erzeugen eines AsynchronousSocketChannel (Zeile 1) besteht der Code im Wesentlichen nur noch aus dem Aufruf der read() Methode des AsynchronousSocketChannel s (Zeile 4).  Der erste Parameter des Methodenaufrufs ist der ByteBuffer , in den die Daten dann asynchron eingelesen werden sollen.  Der dritte und letzte Parameter ist der Callback vom Typ CompletionHander , den wir im Beispiel als Anonymous Inner Class implementieren.  Der read() Aufruf kehrt natürlich sofort zurück, da es sich um asynchrone I/O handelt. Die asynchrone Input-Operation selbst wird vom JDK gemacht, wenn Inputdaten vorhanden sind. Das Anwendungsprogramm bekommt die Notifikation, das die Daten eingelesen wurden und in den ByteBuffer geschrieben worden sind, durch Aufruf der Callback Methode completed() completed() wird von einem Thread aus dem JDK heraus aufgerufen.  Um in der completed() Methode auf den ByteBuffer zugreifen und die eingelesenen Daten verarbeiten zu können, ist die ByteBuffer Variable buf im Kontext der äußeren Funktion final deklariert.

Die Methode failed() aus dem Callback, wird aufgerufen, wenn ein Fehler bei der asynchronen Einleseoperation auftritt, zum Beispiel, wenn die Verbindung abgebrochen worden ist.   Der erste Parameter von failed() gibt Auskunft über die Details des Fehlers.

Bleibt bei der Diskussion des Beispielcodes noch der zweite Parameter der read() Methode.  Er ist in unserem Beispiel null . Der Parameter dient dazu, Daten vom Aufrufkontext an die Callback Methoden durchzuschleusen.  Das heißt, das Objekt, was man bei der read() Methode als zweiten Parameter mitgibt, kommt als zweiter Parameter bei failed() bzw. completed() wieder an.  Man kann sich vorstellen, dass er verwendet wird, um einen Kontext durchzuschleusen, der den Zustand der Kommunikation mit dem Client repräsentiert.  Der Typ des Parameters ist beliebig; alle Methoden, die ihn bekommen, sind generisch.

Der AsynchronousSocketChannel bietet weitere überladene read() Methoden an. Zum Beispiel gibt es eine Variante mit zusätzlichem Timeout, die es erlaubt, das Warten auf den asynchronen Input zeitgesteuert abzubrechen.  Grundsätzlich sind aber alle read() Varianten ähnlich zu benutzen, wie im oben gezeigten Beispiel.

So wie wir unseren Beispielcode oben implementiert haben, wird der Callback von irgendeinem Thread eines Thread Pools innerhalb des JDKs ausgeführt.  Bei der Java 1.4 Lösung  mit dem Selector hatte man Kontrolle über den Thread Pool und seine Threads. Man kann zum Beispiel die Anzahl der Threads, den Namen der Threads, usw. festlegen.  Möchte man diese Form der Kontrolle auch beim AsynchronousSocketChannel   haben,  so gibt es eine überladene Variante der statischen open() Factory Methode des AsynchronousSocketChannel s (Zeile 1).  Diese Methode hat einen Parameter vom Typ AsynchronousChannelGroup .  Die AsynchronousChannelGroup ist nichts anderes als ein selbst definierter Thread Pool, der  beim open() Aufruf an einen oder, was wohl in der Praxis häufiger vorkommt, mehrere AsynchronousSocketChannel s gebunden wird.

Noch ein Hinweis zur Implementierung des AsynchronousSocketChannel :  Diese muss nicht unbedingt, wie man aus dem einleitenden Motivationsteil vielleicht herauslesen mag, auf einer select() bzw. Selector Lösung basieren.  Wenn das Betriebssystem von sich aus eine Callback-Schnittstelle zur Verfügung stellt, wird diese direkt von der Implementierung des AsynchronousSocketChannel genutzt.

Neben der Code-Redundanz ist ein weiterer Kritikpunkt an der Java 1.4 Lösung für asynchrone I/O, dass nur Stream-orientierte Socket-Kommunikation unterstützt wird.  Datagram-orientierte Socket-Kommunikation und Datei I/O konnten bisher nicht asynchron abgewickelt werden.  Dieser Misstand ist mit der NIO 2 in Java 7 nun auch behoben worden:

  • Asynchrone Datagram-orientierte Socket-Kommunikation : Es gibt jetzt einen DatagramChannel mit dem man (zusammen mit einem Selector ) asynchrone, Datagram-orientierte Socket-Kommunikation betreiben kann.  Ein AsynchronousDatagramChannel , der ähnlich wie der AsynchronousSocketChannel , die Asynchronität per Callback (also ohne Selector ) abhandeln kann, war kurz im OpenJDK vorhanden. Er ist dann aber Ende 2010 wieder herausgenommen worden, da er noch nicht wirklich perfekt war und die Zeit bis zum Releasetermin von Java 7 knapp wurde (für Details siehe / ADC /).  Er soll aber mit Java 8 nachgereicht werden.
  • Asynchrone Datei I/O : Für die asynchrone Datei I/O sind beide Abstraktionen FileChannel (zur Benutzung mit Selector ) und AsynchronousFileChannel (Asynchronität per Callback) in Java 7 enthalten.

Zusammenfassung

Die beiden wichtigen Neuerungen der NIO2 in Java 7 sind ein neues File System API und die Erweiterungen für die asynchrone I/O.  Aber auch die kleinen Neuerungen (zum Beispiel TLS 1.2)  können unter Umständen glücklich machen, wenn man darauf gewartet hat.
 
 

Literaturverweise

/ADC/  Beitrag von Alan Bateman auf der nio-dev Mailinglist: Anyone using AsynchronousDatagramChannel?
URL: http://mail.openjdk.java.net/pipermail/nio-dev/2010-October/001122.html
/SDP/ Wikipedia Seite zu SDP
URL: http://de.wikipedia.org/wiki/Sockets_Direct_Protocol
/SDPCNF/ Creating an SDP Configuration File
URL: http://download.oracle.com/javase/tutorial/sdp/sockets/file.html

Die gesamte Serie über Java 7:

/JAVA7-1/ Java 7 - Überblick
Klaus Kreft & Angelika Langer, Java Magazin, Juni 2011
URL: http://www.angelikalanger.com/Articles/EffectiveJava/57.Java7.Overview/57.Java7.Overview.html
/JAVA7-2/ JSR 334 - "Project Coin" (Strings in switch, Exception Handling, ARM, numerische Literale)
Klaus Kreft & Angelika Langer, Java Magazin, August 2011
URL: http://www.angelikalanger.com/Articles/EffectiveJava/58.Java7.Coin1/58.Java7.Coin1.html
/JAVA7-3/ JSR 334 - "Project Coin" (Sprachneuerungen im Zusammenhang mit Generics)
Klaus Kreft & Angelika Langer, Java Magazin, Oktober 2011
URL: http://www.angelikalanger.com/Articles/EffectiveJava/59.Java7.Coin2/59.Java7.Coin2.html
/JAVA7-4/ JSR 203 - "NIO2" (Erweiterung der I/O Funktionalität)
Klaus Kreft & Angelika Langer, Java Magazin, Dezember 2011
URL: http://www.angelikalanger.com/Articles/EffectiveJava/60.Java7.NIO2/60.Java7.NIO2.html
/JAVA7-5/ JSR 166y - Fork-Join-Framework (Teil 1: Internals)
Klaus Kreft & Angelika Langer, Java Magazin, Februar 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/61.Java7.ForkJoin.1/61.Java7.ForkJoin.1.htm
/JAVA7-6/ JSR 166y - Fork-Join-Framework (Teil 2: Benutzung)
Klaus Kreft & Angelika Langer, Java Magazin, April 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/62.Java7.ForkJoin.2/62.Java7.ForkJoin.2.htm
/JAVA7-7/ Thread-Synchronisation mit Hilfe des Phasers
Klaus Kreft & Angelika Langer, Java Magazin, Juni 2012
URL: http://www.angelikalanger.com/Articles/EffectiveJava/63.Java7.Phaser/63.Java7.Phaser.htm

 
 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Effective Java - Advanced Java Programming Idioms 
4 day seminar ( open enrollment and on-site)
 
  © Copyright 1995-2013 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/60.Java7.NIO2/60.Java7.NIO2.html  last update: 24 Jan 2013