1. Tiến trình trong HĐH là gì và tại sao cần khởi chạy chúng từ Java
Tiến trình: từ JVM tới bash và ngược lại
Khi bạn bật máy tính và mở trình duyệt, ứng dụng nhắn tin hay trò chơi — tất cả đều là những tiến trình riêng biệt. Hệ điều hành khởi tạo cho mỗi chương trình một “thế giới mini” riêng: cấp phát bộ nhớ, thời gian CPU, cho phép làm việc với tệp và mạng. Nhờ vậy, các chương trình cùng tồn tại nhưng không cản trở nhau.
Chương trình Java cũng không ngoại lệ. Khi bạn chạy java MyApp, hệ thống tạo một tiến trình riêng với mọi tài nguyên cần thiết. Bên trong đó chương trình của bạn vận hành: tính toán, vẽ, đọc tệp — mọi thứ nó cần làm.
Nhưng đôi khi chỉ Java thôi là chưa đủ. Có lúc bạn cần nhờ một chương trình khác — chạy trình nén để đóng gói tệp, gọi ffmpeg để xử lý video, hoặc đơn giản là kiểm tra phiên bản Java đã cài trên máy. Đó chính là khởi chạy tiến trình bên ngoài: Java nói với hệ thống “hãy gọi tiện ích này giúp tôi”, rồi nhận kết quả đầu ra của nó.
Về bản chất, đây là cách làm chương trình linh hoạt hơn: kết hợp nhiều công cụ, tự động hóa công việc lặp lại, hoặc ghép logic của bạn vào các tiến trình hệ thống sẵn có. Đôi khi nhờ một lệnh bên ngoài làm một phần việc sẽ dễ hơn là “phát minh lại bánh xe” trong mã nguồn.
JVM vs. tiến trình bên ngoài
Tiến trình JVM — chương trình của bạn chạy trong máy ảo Java.
Tiến trình bên ngoài — bất kỳ chương trình nào khác: máy tính bỏ túi, script Python, dòng lệnh, thậm chí một phiên bản Java khác.
2. Lớp ProcessBuilder
“Hồi xưa” Java khởi chạy tiến trình qua phương thức Runtime.getRuntime().exec(). Đây không phải cách thuận tiện hay an toàn nhất — giống như cố đóng đinh bằng kính hiển vi. Vì vậy từ Java 5 xuất hiện lớp ProcessBuilder, cho phép tạo, cấu hình và khởi chạy tiến trình bên ngoài linh hoạt và rõ ràng hơn.
ProcessBuilder là một “bộ dựng” giúp bạn đặt trước mọi tham số của tiến trình tương lai: lệnh, đối số, thư mục làm việc, biến môi trường, v.v.
Cú pháp: tạo tiến trình
ProcessBuilder pb = new ProcessBuilder("lệnh", "đốiSố1", "đốiSố2", ...);
- Đối số đầu tiên — tên lệnh (ví dụ, "ls", "dir", "ping", "java").
- Các đối số còn lại — tham số cho lệnh.
Ví dụ: chạy lệnh ls (Linux/Mac) hoặc dir (Windows)
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
Nhân tiện, trên Windows các lệnh như dir, copy... không phải là tệp thực thi riêng, mà là lệnh “tích hợp” trong dòng lệnh (cmd.exe). Vì thế cần chạy chúng qua cmd.exe /c ....
Ví dụ: khởi chạy một tiến trình đơn giản
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
3. Cấu hình môi trường của tiến trình
Truyền tham số. Tham số của lệnh được truyền dưới dạng các chuỗi riêng biệt:
ProcessBuilder pb = new ProcessBuilder("ping", "google.com");
Chỉ định thư mục làm việc. Mặc định, tiến trình được chạy trong cùng thư mục với chương trình của bạn. Nhưng bạn có thể chỉ định thư mục khác:
pb.directory(new java.io.File("/tmp")); // Dành cho Linux/Mac
pb.directory(new java.io.File("C:\\Temp")); // Dành cho Windows
Thay đổi biến môi trường. Mỗi tiến trình có một tập biến môi trường riêng. Bạn có thể thêm hoặc sửa chúng:
pb.environment().put("MY_VAR", "HelloFromJava");
Điều này hữu ích nếu tiến trình bên ngoài kỳ vọng một số biến cụ thể.
4. Khởi chạy tiến trình
Phương thức start(). Khi đã cấu hình xong, hãy khởi chạy tiến trình:
Process process = pb.start();
Phương thức start() trả về đối tượng Process, cho phép bạn điều khiển chương trình đã khởi chạy: đọc đầu ra, ghi vào đầu vào, kết thúc nó, v.v.
Xử lý ngoại lệ. start() có thể ném IOException nếu lệnh không tồn tại, thiếu quyền hoặc gặp lỗi khởi chạy khác.
Ví dụ:
try {
Process process = pb.start();
// Làm việc với tiến trình...
} catch (IOException e) {
System.out.println("Lỗi khi khởi chạy tiến trình: " + e.getMessage());
}
5. Thực hành: Khởi chạy các lệnh đơn giản
Ví dụ 1: Liệt kê tệp trong thư mục
import java.io.*;
public class ProcessDemo {
public static void main(String[] args) {
// Xác định lệnh tùy theo HĐH
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
try {
Process process = pb.start();
// Đọc đầu ra của tiến trình (stdout)
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// Chờ tiến trình kết thúc
int exitCode = process.waitFor();
System.out.println("Tiến trình kết thúc với mã: " + exitCode);
} catch (IOException | InterruptedException e) {
System.out.println("Lỗi: " + e.getMessage());
}
}
}
Ở đây có gì diễn ra?
- Xác định hệ điều hành để chọn lệnh phù hợp.
- Tạo ProcessBuilder với lệnh cần thiết.
- Khởi chạy tiến trình qua start().
- Đọc từng dòng từ stdout của tiến trình và in ra màn hình.
- Chờ tiến trình kết thúc (waitFor()).
- In mã thoát (0 — thành công, khác 0 — lỗi).
Ví dụ 2: Chạy java -version
ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
Process process = pb.start();
// java -version ghi ra stderr, nên ta đọc getErrorStream()
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
Lưu ý quan trọng: Một số lệnh (ví dụ, java -version) không ghi vào đầu ra tiêu chuẩn (stdout) mà ghi vào luồng lỗi (stderr). Vì vậy đôi khi bạn cần đọc process.getErrorStream().
6. Tính đa nền tảng: khác biệt giữa Windows và Linux/Mac
- Lệnh và tham số của chúng có thể khác nhau.
- Đường dẫn tệp được viết khác nhau (C:\Temp vs /tmp).
- Một số lệnh (ví dụ, ls, cat) chỉ có trên hệ Unix-like, còn Windows dùng các lệnh tương đương (dir, type).
- Trên Windows, các lệnh tích hợp chỉ chạy qua cmd.exe /c lệnh.
Ví dụ kiểm tra HĐH:
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows
} else if (os.contains("mac")) {
// macOS
} else if (os.contains("nix") || os.contains("nux")) {
// Linux
}
Mẹo: Luôn kiểm thử chương trình của bạn trên các HĐH mục tiêu nếu bạn kỳ vọng tính đa nền tảng.
7. Bảng: các phương thức và khả năng chính của ProcessBuilder
| Phương thức/trường | Mục đích | Ví dụ sử dụng |
|---|---|---|
|
Tạo tiến trình với lệnh và tham số | |
|
Đặt thư mục làm việc | |
|
Lấy/thay đổi biến môi trường | |
|
Khởi chạy tiến trình | |
|
Lấy stdout của tiến trình | |
|
Lấy stderr của tiến trình | |
|
Lấy stdin của tiến trình | |
|
Chờ tiến trình kết thúc | |
|
Lấy mã thoát của tiến trình | |
8. Các lỗi thường gặp khi khởi chạy tiến trình bên ngoài
IOException: Cannot run program .... Ví dụ, cố chạy ls trên Windows.
Lỗi số 2: Truyền tham số sai. Đừng gộp cả lệnh vào một chuỗi! Đúng: new ProcessBuilder("ping", "google.com"). Sai: new ProcessBuilder("ping google.com").
Lỗi số 3: Không tính đến khác biệt HĐH. Lệnh chạy tốt trên Linux có thể không tồn tại trên Windows, và ngược lại. Luôn kiểm tra HĐH và điều chỉnh lệnh.
Lỗi số 4: Không xử lý đầu ra của tiến trình. Nếu không đọc đầu ra, tiến trình có thể “treo” do bộ đệm bị đầy. Kể cả khi bạn không dùng đầu ra — hãy đọc và, ví dụ, bỏ qua nó.
Lỗi số 5: Không đóng các luồng. Cần đóng các luồng của tiến trình sau khi dùng để tránh rò rỉ tài nguyên.
Lỗi số 6: Không xử lý ngoại lệ. Khởi chạy tiến trình bên ngoài luôn tiềm ẩn rủi ro. Luôn dùng try-catch và thông báo lỗi cho người dùng.
GO TO FULL VERSION