ComiaTip Prosessit ja tietovirrat


Prosessit ja tietovirrat

Kirjoittanut J-P Julkaistu 16.4.2009

Olen asettanut itselleni säännön, joka menee jotenkin näin: "Älä kutsu käyttöjärjestelmäriippuvia komentoja Java-ohjelmasta". Miksi? Ensinnäkin, jos kutsumme käyttöjärjestelmän omia komentoja tai ulkopuolisia ohjelmia Java-ohjelmasta menetemämme välittömästi yhden javan merkittävimmistä eduista: käyttöjärjestelmäriippumattomuuden ja siirrettävyyden. Toiseksi: voinko olla 100% varma, että koneella, jossa ohjelmaani ajetaan, on asennettu myös tämä käyttämäni ulkoinen ohjelma?

Käyttöjärjestelmäkohtaisten komentojen ajaminen Javasta jakaa mielipiteitä erilaisilla Java-foorumeilla. Osa Java-fanaatikoista pitää sinua hölmönä jos kysyt mitään tähän liittyvää, mutta onneksi useimmat, kuten minä, tietävät että joskus on vain tilanteita joissa käyttöjärjestelmäkohtaisen komennon suorittaminen on välttämätöntä.

Komentojen ajaminen Javan ulkopuolella on helppoa. Jos haluamme esimerkiksi tehdä tiedostolistauksen käyttöjärjestelmän omalla työkalulla voimme tehdä sen näin:

//Linux fanaatikot vaihtakoon komennon tilalle ls. Process listaus = Runtime.getRuntime().exec( new String[]{"cmd.exe","/c","dir"});

Säälittävän helppoa, eikö totta. Valitettavasti useimmat koodarit kuuluvat siihen insinöörien jaloon kastiin, joka ei lue ohjeita ennen kuin on liian myöhäistä. Process-luokan javadoceissa kerrotaan, että ohjelmoijan on Process-olioon liittyvät syöte- ja tulostusvirrat voivat täyttyä hyvin helposti ja tämä voi aiheuttaa erilaisia toimintaongelmia. Oma kokemukseni näistä ongelmista rajoittuu ohjelman mystiseen jämähtämiseen. Koodi toimii toisella laitteistolla ja toisella taas ei.

Ongelma voidaan välttää siten, että Process-olioon liittyvät tietovirrat luetaan mahdollisimman nopeasti, jotta ne eivät pääse täyttymään, tai eivät pysy täysinä maailman tappiin saakka. Tietovirrat (Process.getInputStream() ja Process.getOutputStream()) kannattaa lukea esimerkiksi omassa säikeessään. Esimerkki selkeyttänee asiaa:

import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; public class ProcessReader extends Thread { public static ProcessReader startProcessReader( InputStream input, PrintStream output){ ProcessReader handler = new ProcessReader(input, output); handler.start(); return handler; } private InputStream input; private PrintStream output; private ProcessReader(InputStream input, PrintStream output) { this.input = input; this.output = output; } public void run() { try { InputStreamReader streamReader = new InputStreamReader(input); BufferedReader reader = new BufferedReader(streamReader); //Luetaan data tietovirrasta String line = reader.readLine(); while (line != null) { output.println(line); line = reader.readLine(); } reader.close(); } catch(Exception e) { System.err.println(e); } } }

Main -metodi, jonka avulla toimintaa voi kokeilla Windows koneilla:

public static void main(String args[])throws Exception { Process pro = Runtime.getRuntime().exec( new String[]{"cmd.exe","/c","dir"}); // Luodaan ja käynnistetään säikeet, jotka lukevat // datan prosessin tietovirroista välittömästi ProcessReader.startProcessReader(pro.getInputStream(), System.out); ProcessReader.startProcessReader(pro.getErrorStream(), System.err); }

Koodiesimerkit perustuvat samaa ongelmaa käsittelevään blogi-artikkeliin, joka (kirjoitushetkellä) löytyy osoitteesta http://vyvaks.wordpress.com/2006/05/27/does-runtimeexec-hangs-in-java/