CodeGym /Các khóa học /JAVA 25 SELF /Phân tích các lỗi điển hình khi làm việc với tiến trình

Phân tích các lỗi điển hình khi làm việc với tiến trình

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

1. Deadlock khi đọc/ghi luồng

Một trong những lỗi khó chịu nhất là khi tiến trình bị treo và không kết thúc, dù có vẻ như mọi thứ đã làm đúng. Nguyên nhân thường gặp là tràn bộ đệm của luồng đầu ra hoặc lỗi của tiến trình bên ngoài. Nếu không đọc cả hai luồng (stdout và stderr), tiến trình có thể “bị chặn” khi cố gắng ghi gì đó vì không ai lấy dữ liệu ra khỏi bộ đệm của nó.

Ví dụ về sự cố

ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Chỉ đọc stdout, còn stderr thì bỏ qua!
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}
process.waitFor();

Nếu lệnh ghi gì đó vào stderr (ví dụ, java -version hầu như luôn ghi vào đó!), luồng này sẽ bị tràn bộ đệm và tiến trình bị treo.

Cách làm đúng

Hãy đọc cả hai luồng song song (bằng các thread riêng hoặc ExecutorService):

ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();

// Đọc stdout
Thread stdoutThread = new Thread(() -> {
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(process.getInputStream()))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println("[stdout] " + line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});

// Đọc stderr
Thread stderrThread = new Thread(() -> {
    try (BufferedReader reader = new BufferedReader(
            new InputStreamReader(process.getErrorStream()))) {
        String line;
        while ((line = reader.readLine()) != null) {
            System.err.println("[stderr] " + line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});

stdoutThread.start();
stderrThread.start();

process.waitFor();
stdoutThread.join();
stderrThread.join();

Kết luận:
Nếu không đọc stderr — tiến trình có thể treo.
Nếu không đọc stdout — cũng có thể treo.
Hãy đọc cả hai luồng — và mọi thứ sẽ suôn sẻ!

2. Vấn đề về mã hóa

Các tiến trình bên ngoài có thể dùng mã hóa khác với chương trình Java mặc định của bạn. Nếu không chỉ định đúng mã hóa, bạn sẽ nhận “ký tự rác” thay vì văn bản (đặc biệt dễ thấy với chữ Cyrillic).

Ví dụ lỗi

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

Đoạn mã này dùng mã hóa hệ thống mặc định. Nhưng nếu tiến trình bên ngoài ghi, chẳng hạn, bằng UTF-8, còn của bạn là Windows-1251, chữ Cyrillic sẽ biến thành ký tự lộn xộn.

Cách làm đúng

Hãy chỉ định rõ charset cần dùng nếu bạn biết nó:

BufferedReader reader = new BufferedReader(
    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
);

Nếu không chắc — hãy xem tài liệu của chương trình hoặc thử vài phương án.

Mẹo: trên Windows, các tiện ích dòng lệnh thường dùng CP866 hoặc Windows-1251, còn trên Linux — UTF-8.

3. Khác biệt giữa các nền tảng

Những lệnh chạy được trên một hệ điều hành có thể không tồn tại trên hệ khác. Ví dụ, ls có trên Linux/Mac, nhưng không có trên Windows (ở đó là dir). Cú pháp lệnh, dấu phân tách đường dẫn và dấu ngoặc kép cũng khác nhau.

Ví dụ lỗi

ProcessBuilder builder = new ProcessBuilder("ls", "-l");
builder.start(); // Trên Windows: không tìm thấy "ls"!

Cách làm đúng

String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder builder;
if (os.contains("win")) {
    builder = new ProcessBuilder("cmd", "/c", "dir");
} else {
    builder = new ProcessBuilder("ls", "-l");
}

Đường dẫn tệp: hãy dùng File.separator thay vì “/” hoặc “\” để tránh rắc rối với đường dẫn.

4. Vấn đề về quyền truy cập

Một số lệnh yêu cầu quyền quản trị (xóa tệp hệ thống, thay đổi cấu hình mạng, v.v.). Nếu thiếu quyền, lệnh sẽ kết thúc với lỗi hoặc thậm chí không khởi chạy được.

Ví dụ

ProcessBuilder builder = new ProcessBuilder("rm", "-rf", "/root/secret.txt");
Process process = builder.start();
// ... sẽ nhận lỗi Permission denied

Cách làm đúng

  • Kiểm tra xem lệnh của bạn có cần quyền nâng cao hay không.
  • Xử lý mã thoát của tiến trình qua process.exitValue().
  • Đọc stderr — ở đó thường có nguyên nhân lỗi (ví dụ: “Permission denied”).

5. Rò rỉ tài nguyên

Nếu không đóng các luồng của tiến trình (InputStream, OutputStream, ErrorStream), chúng có thể “treo”, chiếm tài nguyên và thậm chí chặn việc kết thúc. Tương tự, nếu không kết thúc chính tiến trình, nó có thể trở thành “zombie” trong hệ thống.

Ví dụ lỗi

Process process = builder.start();
// ... chạy nhưng không đóng các luồng!

Cách làm đúng

Hãy dùng try-with-resources cho các luồng:

try (BufferedReader reader = new BufferedReader(
         new InputStreamReader(process.getInputStream()))) {
    // Đọc output
}

Sau khi xong việc với tiến trình, hãy dừng nó một cách đúng đắn:

process.destroy(); // Kết thúc tiến trình (nếu nó vẫn còn chạy)

Lưu ý: nếu không đóng các luồng — có thể xảy ra rò rỉ bộ nhớ, treo và vấn đề hệ thống (đặc biệt khi chạy nhiều tiến trình).

6. Deadlock khi tương tác

Khi trao đổi tương tác, rất dễ rơi vào tình huống khóa lẫn nhau: Java chờ phản hồi, còn tiến trình bên ngoài — chờ dữ liệu của bạn. Kết quả là cả hai đều “im lặng”. Hoặc bạn đã gửi dữ liệu nhưng không đọc phản hồi — đến lúc nào đó bộ đệm của chương trình bên ngoài bị đầy và nó dừng ghi.

Để tránh điều này, hãy tách nhiệm vụ: một thread phụ trách đọc, thread kia — ghi. Khi đó tương tác diễn ra song song và không ai chặn ai. Đồng thời đừng để mở các luồng sau khi kết thúc — hãy đóng chúng để hệ thống không giữ tài nguyên thừa.

1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Xâm nhập vào Khu Vực Bảo Mật 🚫
Xâm nhập vào Khu Vực Bảo Mật 🚫
1
Nhiệm vụ
JAVA 25 SELF, mức độ, bài học
Đã khóa
Con mắt toàn năng của Thám tử 🕵️
Con mắt toàn năng của Thám tử 🕵️
1
Khảo sát/đố vui
, cấp độ , bài học
Không có sẵn
Làm việc với process
Làm việc với process
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION