Treballant amb processos a Java (I)


Abans d'entrar en ple sobre com executar processos amb java i com tenir-ne controlades les seves possibles excepcions, fem una breu introducció perquè ens quedin clars alguns conceptes:

Un procés és un programa corrent sota el control del Sistema operatiu.
El sistema operatiu inicia el procés, aquest normalment corre per separat com un fil de computació i per descomptat, l'entrada i la sortida d'aquest procés està controlat pel SO.

Per exemple quan nosaltres executem a la línia de comandes:
javac ProgramaQualsevol.java
el Sistema Operatiu inicia un procés amb el compilador java i li passa la línia "ProgramaQualsevol.java" com argument. En aquest cas el programa javac no necessita res més que els fitxers .java i .class. És a dir, no espera entrada per teclat ('stdin') per exemple. La sortida del programa javac ens la proporciona el SO per canal estàndard ('stdout').
Si es produeix un error en l'execució del programa javac, o l'interrupem (amb el típic Control+C), s'envien tota la llista d'errors (ho fa el SO) a la sortida estàndard que tenim d'errors ('stderr').

Hi ha vàries maneres de treballar amb processos a Java. Avui presento la més simple, però la menys controlable, que permet executar comandes externes al programa. A la següent entrada parlarem dels programes multithread i com es gestionen els diferents fils que treballen sobre un mateix programa.



Creant processos a Java

Podem crear nous processos des del nostre programa Java utilitzant la classe java.lang.Runtime, cal destacar però, que per motius de seguretat els applets no poden crear processos. I què és la classe Runtime? Cada aplicació java té només una instància de la classe Runtime que permet l'aplicació per interactuar amb l'entorn en el qual s'executa l'aplicació. L'actual Runtime, per exemple, podem obtenir-lo amb el mètode getRuntime().
A continuació us introdueixo els mètodes que considero més rellevants o si més no, amb les que treballarem a continuació:
  • public static Runtime getRuntime()
Retorna l'objecte Runtime associat amb l'actual aplicació Java. Molts dels mètodes de la classe són mètodes d'instància i s'han d'invocar sobre l'objecte d'execució actual.
  • public Process exec(String command) throws IOException 
Executa la comanda específica d'objecte String en un procés separat.

Com podeu començar a intuir doncs, a Java podem córrer comandes Unix. Per exemple, la coneguda 'ls', per llistar directoris, l'executaríem de la següent manera:
Runtime r = Runtime.getRuntime();                                          
Process p = r.exec("ls");  
I ara la pregunta... com obtenim els resultats? la classe abstracta Process ens facilita un seguit de mètodes a part dels utilitzats anteriorment, es poden consultar tots ells a l'API. El mètode Runtime.exe crea un procés natiu i retorna una instància d'una subclasse de Process que pot ser utilitzada per controlar-lo i obtenir informació d'aquest. Presento, doncs, nous mètodes de la classe:
  • int exitValue()
  • InputStream getErrorStream()
  • InputStream getInputStream()
  • OuputStream getOutpuStream()
Si encara no heu treballat amb Input/Ouput Stream, us recomano que consulteu pel vostre compte l'API de Java.
Ara hem de fer un petit esforç de canvi de concepte, el getInputStream ens donarà l'Input connectat a la sortida del procés, al revés que el getOutputStream que ens donaria la sortida connectada a l'entrada del procés, ideal per passar paràmetres.
Runtime r = Runtime.getRuntime();
Process p = r.exec("ls"); 
InputStream in = p.getInputStream(); 
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String l; 
while ((l = br.readLine()) != null){
    System.out.println(l);
}
br.close();
 Podem veure en el codi que després es pot treballar amb cada una de les línies de la sortida de la comanda fàcilment.

Gestionant els processos

Aquest seria el cas idoni, quan tot s'ha desenvolupat correctament i la nostra comanda acaba amb èxit. L'interessant és poder gestionar el procés que executem i és en aquest punt on introduïm un nou mètode:
  • int waitFor() throws InterruptedException
Aquest mètode obliga al thread actual, i per tant el que llança l'execució de laltre/s, esperar a que acabi el procés que representa l'objecte Process p. Aquest mètode retorna immediatament si el procés ha acabat o no; en cas negatiu, el thread principal es quedarà bloquejat fins que el subprocés acabi.
És obligatori esperar a que el procés acabi...? No clar, però si seguim endavant amb les daves d'un subprocés i aquest encara no ha acabat, segurament obtindrem una excepció. Anem a veure-ho:
p.waitFor();
int ev = p.exitValue();
System.out.println("ls exited with: "+ev);
Executem la comanda waitFor() sobre el procés, el thread principal s'esperarà aquí i a l'acabar, executarà el següent mètode (quin ha estat el valor de finalització). Què passaria si eliminem la primera línia? El thread tiraria endavant i al preguntar l'exitValue quan el procés, segurament encara no ha acabat, saltaria una excepció:

java.lang.IllegalThreadStateException:
process hasn’t exited
java.lang.UNIXProcess.exitValue (UNIXProcess.java:220)

Ai... el preu de l'impaciència!

Enviar dades al procés

Per acabar l'entrada d'avui, exposaré l'últim pilar bàsic per començar en aquesta gestió: com passar paràmetres al nou procés creat. El procediment és similar al que hem vist en l'obtenció de la sortida, veiem-ne un exemple:
Process p = r.exec("grep");
OutputStream out = grepProc.getOutputStream();
PrintWriter w = new PrintWriter(new OutputStreamWriter(out));
w.println("A buscar");
w.println("/etc/usr/files");
w.close();
Veiem com ens permet enviar paràmetres a qualsevol crida que ho necessiti.
Si treballem gestionant els streams i tenim clar com funcionen, es pot arribar a construir pipes entres processos d'aquesta manera. És a dir, ser capaços de concatenar les sortides d'un subprocés creat amb java amb l'entrada d'un altre.
Anem a recuperar els exemples anteriors, volem que la sortida del ls sigui l'entrada d'un grep cap a java:

//preparem el proces de ls i la seva sortida
lsP = rt.exec("ls");
in = lsP.getInputStream();

//preparem el procés de grep i la seva entrada
grepP = rt.exec("grep java");
out = grepP.getOutputStream();
int b;

//llegirem les linies de sortida de ls i les anirem passant al grep
while((b = in.read()) != -1){
    out.write(b);
}
lsP.waitFor();
in.close();
out.close();
in = grepP.getInputStream();

//per ultim caldrà quedar-se amb la sortida del grep
while((b = in.read()) != -1){
    System.out.write(b);
}
grepP.waitFor();
in.close();
En la pròxima entrada veurem com treballar amb diferents threads (multihreading) dins la mateixa execució, els semàfors i la memòria compartida.

Els exemples aquí presentats són una adaptació de l'arxiu "Working with Processes in Java" de la Middle East Technical University (METU).




0 Comentarios