Java中Process执行命令导致GUI界面无法刷新问题解决

﹏ヽ暗。殇╰゛Y 2023-07-06 06:22 42阅读 0赞
  1. 今天为了测试写了个小程序,一个小窗口,点击按钮时会复制一个大文件,通过Process执行cmd命令,然后界面显示“正在复制”,复制完了读取文件大小并显示“复制完成”。先开始我在按钮事件里面这么写:
  2. JButton b=new JButton("点击启动");
  3. b.addActionListener(new ActionListener() {
  4. public void actionPerformed(ActionEvent arg0) {
  5. try {
  6. l.setText("正在复制");
  7. l.setBounds(127, 50, 125, 19);
  8. Process p=Runtime.getRuntime().exec("cmd /c copy /y \"D:\\工具\\系统镜像\\deepin-15.11-64位.iso\" \"E:\\中转\\deepin-15.11-64位.iso\"");
  9. System.out.println(new File("E:\\中转\\deepin-15.11-64位.iso").length());
  10. l.setText("复制完成");
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. });

我想让它在复制的时候显示“正在复制”,完成后显示“复制完成”,并显示复制后文件大小。

结果点击按钮后直接显示复制完成,显示大小为0。

原来点击按钮时,Process p…语句及下面语句一起执行了,还没等复制完,他就显示“复制完成”并读取大小了。我想要等Process执行完了再执行下面的读取大小和显示“复制完成”字样,当然Process类里面有waitFor()方法,放在Process下面,就可以实现等待命令执行完了再向下走,于是我改成这样:

  1. JButton b=new JButton("点击启动");
  2. b.addActionListener(new ActionListener() {
  3. public void actionPerformed(ActionEvent arg0) {
  4. try {
  5. l.setText("正在复制");
  6. l.setBounds(127, 50, 125, 19);
  7. Process p=Runtime.getRuntime().exec("cmd /c copy /y \"D:\\工具\\系统镜像\\deepin-15.11-64位.iso\" \"E:\\中转\\deepin-15.11-64位.iso\"");
  8. p.waitFor(); //执行命令后等待期完成再向下执行
  9. System.out.println(new File("E:\\中转\\deepin-15.11-64位.iso").length());
  10. l.setText("复制完成");
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. });

结果我发现,窗口连字都不显示了,按钮也卡住了无法操作。查查资料,发现是waitFor()导致进程阻塞。参考JDK文档得知:如有必要,要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。

所以问题出在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。

  1. 先要知道,当Runtime对象调用exec(命令)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。

程序通过标准输入流会不断向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitFor()这里。

一、解决waitFor()线程阻塞

先来解决waitFor()阻塞的问题。其实很简单,只要新建两个线程,一个读取标准输入,一个读取标准错误,在waitFor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容不就行了吗?

于是改代码为这样:

  1. JButton b=new JButton("点击启动");
  2. b.addActionListener(new ActionListener() {
  3. public void actionPerformed(ActionEvent arg0) {
  4. try {
  5. l.setText("正在复制");
  6. l.setBounds(127, 50, 125, 19);
  7. Process p=Runtime.getRuntime().exec("cmd /c copy /y \"D:\\工具\\系统镜像\\deepin-15.11-64位.iso\" \"E:\\中转\\deepin-15.11-64位.iso\"");
  8. new Thread() { //线程1:读取标准输入流
  9. public void run() {
  10. InputStreamReader isr=new InputStreamReader(p.getInputStream()); //获取标准输入流
  11. BufferedReader br=new BufferedReader(isr);
  12. try {
  13. while(br.readLine()!=null) {
  14. //只要有数据就一直读取,但不执行任何操作
  15. }
  16. } catch(Exception e) {
  17. e.printStackTrace();
  18. } finally {
  19. try {
  20. br.close(); //最后记得关闭读取流
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  26. }.start();
  27. new Thread() { //线程2:读取标准错误流
  28. public void run() {
  29. InputStreamReader isr=new InputStreamReader(p.getErrorStream()); //获取标准错误流
  30. BufferedReader br=new BufferedReader(isr);
  31. try {
  32. while(br.readLine()!=null) {
  33. //只要有数据就一直读取,但不执行任何操作
  34. }
  35. } catch(Exception e) {
  36. e.printStackTrace();
  37. } finally {
  38. try {
  39. br.close(); //最后记得关闭读取流
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. }
  45. }.start();
  46. p.waitFor(); //执行命令后等待期完成再向下执行
  47. System.out.println(new File("E:\\中转\\deepin-15.11-64位.iso").length());
  48. l.setText("复制完成");
  49. } catch (Exception e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. });

这样,过一会发现显示复制完成了并且显示了正确的文件大小。

二、GUI文字刷新问题

但是还是有个问题:点击按钮的时候应该显示“正在复制”,结果点击按钮什么都没显示。复制完成后,waitFor()语句后面的才正常执行。查了资料才发现,JButton的事件里面语句其实都是在一个线程里面按顺序执行的。而在这个按钮事件里面,设置JLabel文字和waitFor()语句在一起,waitFor()为了等待Process执行完便使线程暂时停止,导致设置文字显示语句没有机会执行,也就是说waitFor()和上面的设置文字语句发生冲突。那有人会问:刚刚不是新建两个线程了吗?

刚刚建的线程只是用于读取标准输入和标准错误流使得语句可以顺利执行下去,不让他卡死在waitFor()那里,使得程序可以向下执行。但是设置文字的语句和waitFor()还是在一个线程里面啊!他们两个也会相互作用。

既然这样,我只好又新建一个线程,把waitFor()和设置文字语句分开,先设置文字“正在复制”并执行Process,然后新建一个线程,把刚刚下面语句全部装进新线程:

  1. b.addActionListener(new ActionListener() {
  2. public void actionPerformed(ActionEvent arg0) {
  3. try {
  4. l.setText("正在复制");
  5. l.setBounds(127, 50, 125, 19);
  6. Process p=Runtime.getRuntime().exec("cmd /c copy /y \"D:\\工具\\系统镜像\\deepin-15.11-64位.iso\" \"E:\\中转\\deepin-15.11-64位.iso\"");
  7. new Thread() { //为后续再建一线程
  8. public void run() {
  9. new Thread() { //线程1:读取标准输入流
  10. public void run() {
  11. InputStreamReader isr=new InputStreamReader(p.getInputStream()); //获取标准输入流
  12. BufferedReader br=new BufferedReader(isr);
  13. try {
  14. while(br.readLine()!=null) {
  15. //只要有数据就一直读取,但不执行任何操作
  16. }
  17. } catch(Exception e) {
  18. e.printStackTrace();
  19. } finally {
  20. try {
  21. br.close(); //最后记得关闭读取流
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }.start();
  28. new Thread() { //线程2:读取标准错误流
  29. public void run() {
  30. InputStreamReader isr=new InputStreamReader(p.getErrorStream()); //获取标准错误流
  31. BufferedReader br=new BufferedReader(isr);
  32. try {
  33. while(br.readLine()!=null) {
  34. //只要有数据就一直读取,但不执行任何操作
  35. }
  36. } catch(Exception e) {
  37. e.printStackTrace();
  38. } finally {
  39. try {
  40. br.close(); //最后记得关闭读取流
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. }.start();
  47. try {
  48. p.waitFor();
  49. p.destroy();
  50. } catch (Exception e) {
  51. e.printStackTrace();
  52. }
  53. System.out.println(new File("E:\\中转\\deepin-15.11-64位.iso").length());
  54. l.setText("复制完成");
  55. }
  56. }.start();
  57. } catch (Exception e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. });

上面是一个线程里面又新建了两个线程,要仔细看清,理清思路。

这样,两个问题都解决了!

总之,解决这些问题的关键,就是通过新建线程,把会发生阻塞的,会相互死锁的语句分开即可!

发表评论

表情:
评论列表 (有 0 条评论,42人围观)

还没有评论,来说两句吧...

相关阅读