CodeGym /Các khóa học /JAVA 25 SELF /ProcessBuilder — khởi chạy tiến trình bên ngoài

ProcessBuilder — khởi chạy tiến trình bên ngoài

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

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
new ProcessBuilder(String...)
Tạo tiến trình với lệnh và tham số
new ProcessBuilder("ls", "-l")
.directory(File)
Đặt thư mục làm việc
.directory(new File("/tmp"))
.environment()
Lấy/thay đổi biến môi trường
.environment().put("VAR", "value")
.start()
Khởi chạy tiến trình
Process p = pb.start()
Process.getInputStream()
Lấy stdout của tiến trình
InputStream
Process.getErrorStream()
Lấy stderr của tiến trình
InputStream
Process.getOutputStream()
Lấy stdin của tiến trình
OutputStream
Process.waitFor()
Chờ tiến trình kết thúc
int code = p.waitFor()
Process.exitValue()
Lấy mã thoát của tiến trình
int code = p.exitValue()

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.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION