Ketika Runtime.exec () tidak mau

Sebagai bagian dari bahasa Java, java.langpaket tersebut diimpor secara implisit ke dalam setiap program Java. Masalah paket ini sering muncul, memengaruhi sebagian besar pemrogram. Bulan ini, saya akan membahas jebakan yang mengintai dalam Runtime.exec()metode ini.

Pitfall 4: When Runtime.exec () tidak mau

Kelas ini java.lang.Runtimememiliki fitur metode statis yang dipanggil getRuntime(), yang mengambil Java Runtime Environment saat ini. Itulah satu-satunya cara untuk mendapatkan referensi ke Runtimeobjek tersebut. Dengan referensi tersebut, Anda dapat menjalankan program eksternal dengan menjalankan metode Runtimekelas exec(). Pengembang sering menyebut metode ini untuk meluncurkan browser untuk menampilkan halaman bantuan dalam HTML.

Ada empat versi exec()perintah yang kelebihan beban :

  • public Process exec(String command);
  • public Process exec(String [] cmdArray);
  • public Process exec(String command, String [] envp);
  • public Process exec(String [] cmdArray, String [] envp);

Untuk masing-masing metode ini, sebuah perintah - dan mungkin sekumpulan argumen - diteruskan ke panggilan fungsi khusus sistem operasi. Ini kemudian membuat proses khusus sistem operasi (program yang berjalan) dengan referensi ke Processkelas yang dikembalikan ke Java VM. The Processclass adalah kelas abstrak, karena subclass spesifik Processada untuk setiap sistem operasi.

Anda dapat memberikan tiga kemungkinan parameter masukan ke dalam metode ini:

  1. String tunggal yang mewakili program yang akan dijalankan dan argumen apa pun untuk program itu
  2. Larik string yang memisahkan program dari argumennya
  3. Array variabel lingkungan

Meneruskan variabel lingkungan dalam formulir name=value. Jika Anda menggunakan versi exec()with string tunggal untuk program dan argumennya, perhatikan bahwa string tersebut diuraikan menggunakan spasi sebagai pembatas melalui StringTokenizerkelas.

Tersandung ke IllegalThreadStateException

Jebakan pertama yang terkait dengan Runtime.exec()adalah IllegalThreadStateException. Tes pertama yang lazim dari sebuah API adalah mengkodekan metode yang paling jelas. Misalnya, untuk menjalankan proses di luar VM Java, kami menggunakan exec()metode. Untuk melihat nilai yang dikembalikan oleh proses eksternal, kami menggunakan exitValue()metode di Processkelas. Dalam contoh pertama kami, kami akan mencoba mengeksekusi compiler Java ( javac.exe):

Daftar 4.1 BadExecJavac.java

import java.util. *; impor java.io. *; kelas publik BadExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Proses proc = rt.exec ("javac"); int exitVal = proc.exitValue (); System.out.println ("Process exitValue:" + exitVal); } menangkap (Throwable t) {t.printStackTrace (); }}}

Serangkaian hasil BadExecJavac:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java BadExecJavac java.lang.IllegalThreadStateException: proses belum keluar di java.lang.Win32Process.exitValue (Metode Asli) di BadExecJavac.main (BadExecJavac.java:13) 

Jika proses eksternal belum selesai, exitValue()metode ini akan menampilkan IllegalThreadStateException; itulah mengapa program ini gagal. Sementara dokumentasi menyatakan fakta ini, mengapa metode ini tidak bisa menunggu hingga bisa memberikan jawaban yang valid?

Peninjauan yang lebih menyeluruh pada metode yang tersedia di Processkelas mengungkapkan waitFor()metode yang melakukannya dengan tepat. Bahkan, waitFor()juga mengembalikan nilai keluar, yang berarti bahwa Anda tidak akan menggunakan exitValue()dan waitFor()berhubungan satu sama lain, melainkan akan memilih satu atau yang lain. Satu-satunya waktu yang mungkin Anda gunakan exitValue()daripada waitFor()saat Anda tidak ingin program Anda memblokir menunggu proses eksternal yang mungkin tidak pernah selesai. Alih-alih menggunakan waitFor()metode ini, saya lebih suka meneruskan parameter boolean yang dipanggil waitForke exitValue()metode untuk menentukan apakah utas saat ini harus menunggu atau tidak. Boolean akan lebih menguntungkan karenaexitValue()adalah nama yang lebih sesuai untuk metode ini, dan dua metode tidak perlu menjalankan fungsi yang sama dalam kondisi yang berbeda. Diskriminasi kondisi sederhana seperti itu adalah domain parameter input.

Oleh karena itu, untuk menghindari jebakan ini, tangkap IllegalThreadStateExceptionatau tunggu hingga proses selesai.

Sekarang, mari kita perbaiki masalah di Listing 4.1 dan tunggu prosesnya selesai. Dalam Listing 4.2, program mencoba lagi untuk mengeksekusi javac.exedan kemudian menunggu proses eksternal selesai:

Daftar 4.2 BadExecJavac2.java

import java.util. *; impor java.io. *; kelas publik BadExecJavac2 {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Proses proc = rt.exec ("javac"); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } menangkap (Throwable t) {t.printStackTrace (); }}}

Sayangnya, proses BadExecJavac2tidak menghasilkan output. Program hang dan tidak pernah selesai. Mengapa javacprosesnya tidak pernah selesai?

Mengapa Runtime.exec () hang

Dokumentasi Javadoc JDK memberikan jawaban untuk pertanyaan ini:

Karena beberapa platform asli hanya menyediakan ukuran buffer terbatas untuk aliran input dan output standar, kegagalan untuk segera menulis aliran input atau membaca aliran output dari subproses dapat menyebabkan subproses diblokir, dan bahkan kebuntuan.

Apakah ini hanya kasus programmer tidak membaca dokumentasi, seperti yang tersirat dalam saran yang sering dikutip: baca manual halus (RTFM)? Jawabannya sebagian ya. Dalam kasus ini, membaca Javadoc akan membuat Anda setengah jalan; ini menjelaskan bahwa Anda perlu menangani aliran ke proses eksternal Anda, tetapi tidak memberi tahu Anda caranya.

Variabel lain berperan di sini, seperti yang dibuktikan dengan banyaknya pertanyaan programmer dan kesalahpahaman tentang API ini di newsgroup: meskipun Runtime.exec()dan Process API tampak sangat sederhana, kesederhanaan itu menipu karena penggunaan API yang sederhana, atau jelas, rentan terhadap kesalahan. Pelajaran di sini bagi perancang API adalah memesan API sederhana untuk operasi sederhana. Operasi yang rentan terhadap kompleksitas dan ketergantungan khusus platform harus mencerminkan domain secara akurat. Mungkin saja abstraksi dibawa terlalu jauh. The JConfigperpustakaan memberikan contoh dari API yang lebih lengkap untuk menangani file dan proses operasi (lihat Sumberdaya di bawah untuk informasi lebih lanjut).

Sekarang, mari ikuti dokumentasi JDK dan tangani output dari javacproses tersebut. Ketika Anda menjalankan javactanpa argumen, ini menghasilkan satu set pernyataan penggunaan yang menjelaskan bagaimana menjalankan program dan arti dari semua opsi program yang tersedia. Mengetahui bahwa ini akan stderrmengalir, Anda dapat dengan mudah menulis program untuk membuang aliran tersebut sebelum menunggu proses untuk keluar. Kode 4.3 menyelesaikan tugas itu. Meskipun pendekatan ini akan berhasil, ini bukanlah solusi umum yang baik. Jadi, program Listing 4.3 diberi nama MediocreExecJavac; itu hanya memberikan solusi yang biasa-biasa saja. Solusi yang lebih baik akan mengosongkan aliran kesalahan standar dan aliran keluaran standar. Dan solusi terbaik akan mengosongkan aliran ini secara bersamaan (saya akan menunjukkannya nanti).

Daftar 4.3 MediocreExecJavac.java

import java.util. *; impor java.io. *; kelas publik MediocreExecJavac {public static void main (String args []) {try {Runtime rt = Runtime.getRuntime (); Proses proc = rt.exec ("javac"); InputStream stderr = proc.getErrorStream (); InputStreamReader isr = InputStreamReader baru (stderr); BufferedReader br = new BufferedReader (isr); Garis string = null; System.out.println (""); sementara ((baris = br.readLine ())! = null) System.out.println (baris); System.out.println (""); int exitVal = proc.waitFor (); System.out.println ("Process exitValue:" + exitVal); } menangkap (Throwable t) {t.printStackTrace (); }}}

Serangkaian MediocreExecJavacmenghasilkan:

E: \ class \ com \ javaworld \ jpitfalls \ article2> java MediocreExecJavac Penggunaan: javac di mana termasuk: -g Hasilkan semua info debugging -g: none Hasilkan tidak ada info debugging -g: {lines, vars, source} Hasilkan hanya beberapa info debugging -O Optimalkan; dapat menghalangi debugging atau memperbesar file kelas -nowarn Tidak menghasilkan peringatan -verbose Pesan keluaran tentang apa yang dilakukan compiler -deprecation Lokasi sumber keluaran di mana API yang tidak digunakan lagi digunakan -classpath Tentukan tempat untuk menemukan file kelas pengguna -sourcepath Tentukan di mana menemukan file sumber masukan -bootclasspath Mengganti lokasi file kelas bootstrap -extdirs Menimpa lokasi ekstensi yang diinstal -d Tentukan di mana menempatkan file kelas yang dihasilkan -encoding Tentukan pengkodean karakter yang digunakan oleh file sumber -target Menghasilkan file kelas untuk versi VM tertentu Proses exitValue: 2

So, MediocreExecJavac works and produces an exit value of 2. Normally, an exit value of 0 indicates success; any nonzero value indicates an error. The meaning of these exit values depends on the particular operating system. A Win32 error with a value of 2 is a "file not found" error. That makes sense, since javac expects us to follow the program with the source code file to compile.

Thus, to circumvent the second pitfall -- hanging forever in Runtime.exec() -- if the program you launch produces output or expects input, ensure that you process the input and output streams.

Assuming a command is an executable program

Under the Windows operating system, many new programmers stumble upon Runtime.exec() when trying to use it for nonexecutable commands like dir and copy. Subsequently, they run into Runtime.exec()'s third pitfall. Listing 4.4 demonstrates exactly that:

Listing 4.4 BadExecWinDir.java

import java.util.*; import java.io.*; public class BadExecWinDir { public static void main(String args[]) { try { Runtime rt = Runtime.getRuntime(); Process proc = rt.exec("dir"); InputStream stdin = proc.getInputStream(); InputStreamReader isr = new InputStreamReader(stdin); BufferedReader br = new BufferedReader(isr); String line = null; System.out.println(""); while ( (line = br.readLine()) != null) System.out.println(line); System.out.println(""); int exitVal = proc.waitFor(); System.out.println("Process exitValue: " + exitVal); } catch (Throwable t) { t.printStackTrace(); } } } 

A run of BadExecWinDir produces:

E:\classes\com\javaworld\jpitfalls\article2>java BadExecWinDir java.io.IOException: CreateProcess: dir error=2 at java.lang.Win32Process.create(Native Method) at java.lang.Win32Process.(Unknown Source) at java.lang.Runtime.execInternal(Native Method) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at java.lang.Runtime.exec(Unknown Source) at BadExecWinDir.main(BadExecWinDir.java:12) 

Seperti yang dinyatakan sebelumnya, nilai kesalahan dari 2means "file not found", yang, dalam hal ini, berarti executable bernama dir.exetidak dapat ditemukan. Itu karena perintah direktori adalah bagian dari penerjemah perintah Windows dan bukan dapat dijalankan secara terpisah. Untuk menjalankan penerjemah perintah Windows, jalankan salah satu command.comatau cmd.exe, bergantung pada sistem operasi Windows yang Anda gunakan. Kode 4.5 menjalankan salinan penerjemah perintah Windows dan kemudian menjalankan perintah yang disediakan pengguna (misalnya, dir).

Daftar 4.5 GoodWindowsExec.java