java打开本地应用程序(调用cmd)---Runtime用法详解

  有时候我们需要借助java程序打开电脑自带的一些程序,可以直接打开或者借助cmd命令窗口打开一些常用的应用程序或者脚本,在cmd窗口执行的命令都可以通过这种方式运行。

例如:

package cn.xm.exam.test;

import java.io.IOException;

import org.junit.Test;

public class TestCmd {
    @Test
    public void test1() throws IOException {
        // 直接打开应用程序
        Runtime.getRuntime().exec("C:/Users/liqiang/Desktop/开机后点它.bat"); // 打开一个批处理文件
        Runtime.getRuntime().exec("E:/酷狗/KGMusic/KuGou.exe"); // 打开酷狗

        /******** 可以通过cmd命令打开软件或者是做其他 *****/
        Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe /k start E:/酷狗/KGMusic/KuGou.exe"); // 通过cmd窗口执行命令
        Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe /k start E:/php/Test/第一个html/界面.html"); // 通过cmd命令打开一个网页
        Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe /k mkdir C:\\Users\\liqiang\\Desktop\\java键的1"); // 通过cmd创建目录用两个反斜杠
        Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe /k mkdir C:\\Users\\liqiang\\Desktop\\java键的2"); // 通过cmd创建目录用两个反斜杠
        Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe /c calc ");// 通过cmd打开计算器
    }

    @Test
    public void test2() throws IOException {
        /******** 可以通过cmd命令打开软件或者是做其他 *****/
        Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe /c osk");// 通过屏幕软键盘
    }

}

 另外也可以获取一些其他的JVM参数:

        long totalMemory = Runtime.getRuntime().totalMemory();//总内存
        long freeMemory = Runtime.getRuntime().freeMemory();//剩余内存
        long maxMemory = Runtime.getRuntime().maxMemory();//最大内存
        System.out.println(totalMemory/1024/1024+"MB");
        System.out.println(freeMemory/1024/1024+"MB");
        System.out.println(maxMemory/1024/1024+"MB");

 也可以直接执行一些命令:

Runtime.getRuntime().exec("calc");//打开计算器

补充:上面的方式都是异步运行的方式,也就是在执行命令之后会不等exec执行完就执行下一条语句,为了实现同步结果,或者为了获取返回的结果,参考:

import java.io.IOException;
import java.io.InputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class Test {
    private static final Logger logger = LoggerFactory.getLogger(Test.class);

    public static void main(String[] args) throws NullPointerException {
        long start = System.currentTimeMillis();
        String srcPath = "C:/Users/liqiang/Desktop/ww/tt.docx", desPath = "C:/Users/liqiang/Desktop/ww";
        String command = "";
        String osName = System.getProperty("os.name");
        if (osName.contains("Windows")) {
            command = "soffice --headless --convert-to pdf " + srcPath + " --outdir " + desPath;
            exec(command);
        }
        long end = System.currentTimeMillis();
        logger.debug("用时:{} ms", end - start);
    }

    public static boolean exec(String command) {
        Process process;// Process可以控制该子进程的执行或获取该子进程的信息
        try {
            logger.debug("exec cmd : {}", command);
            process = Runtime.getRuntime().exec(command);// exec()方法指示Java虚拟机创建一个子进程执行指定的可执行程序,并返回与该子进程对应的Process对象实例。
            // 下面两个可以获取输入输出流
            InputStream errorStream = process.getErrorStream();
            InputStream inputStream = process.getInputStream();
        } catch (IOException e) {
            logger.error(" exec {} error", command, e);
            return false;
        }

        int exitStatus = 0;
        try {
            exitStatus = process.waitFor();// 等待子进程完成再往下执行,返回值是子线程执行完毕的返回值
            // 第二种接受返回值的方法
            int i = process.exitValue(); // 接收执行完毕的返回值
            logger.debug("i----" + i);
        } catch (InterruptedException e) {
            logger.error("InterruptedException  exec {}", command, e);
            return false;
        }

        if (exitStatus != 0) {
            logger.error("exec cmd exitStatus {}", exitStatus);
        } else {
            logger.debug("exec cmd exitStatus {}", exitStatus);
        }

        process.destroy(); // 销毁子进程
        process = null;

        return true;
    }

}

 参考:https://www.cnblogs.com/qlqwjy/p/9846904.html

补充:runtime执行的时候也可以获取其输出流与错误的输出流,也就是每次在调用runtime的时候比较耗时,其会创建一个Process,并且伴随着两个流。(InputStream可以获取到类似于在cmd运行的时候获取到的信息,这在用java写一些脚本的时候非常有用)

package com.test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

public class MyTest {
    public static void main(String[] args) {
        try {
            Process pop = Runtime.getRuntime()
                    .exec("E:/weblogic12.1.3/user_projects/domains/base_domain/startWebLogic.cmd");
            // 获取其正常的输出流
            InputStream inputStream = pop.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader br = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }

            // 获取其错误的输出流
            InputStream errorStream = pop.getErrorStream();
            InputStreamReader errorStreamReader = new InputStreamReader(errorStream);
            BufferedReader errorBr = new BufferedReader(errorStreamReader);
            String errorLine = null;
            while ((errorLine = errorBr.readLine()) != null) {
                System.out.println("err:" + errorLine);
            }

            pop.waitFor();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:

.
.
JAVA Memory arguments: -Xms256m -Xmx512m -XX:CompileThreshold=8000 -XX:PermSize=128m  -XX:MaxPermSize=256m
.
CLASSPATH=C:\PROGRA~1\Java\JDK17~1.0_8\lib\tools.jar;E:\WEBLOG~1.3\wlserver\server\lib\weblogic_sp.jar;E:\WEBLOG~1.3\wlserver\server\lib\weblogic.jar;E:\WEBLOG~1.3\oracle_common\modules\net.sf.antcontrib_1.1.0.0_1-0b3\lib\ant-contrib.jar;E:\WEBLOG~1.3\wlserver\modules\features\oracle.wls.common.nodemanager_2.0.0.0.jar;E:\WEBLOG~1.3\oracle_common\modules\com.oracle.cie.config-wls-online_8.1.0.0.jar;E:\WEBLOG~1.3\wlserver\common\derby\lib\derbyclient.jar;E:\WEBLOG~1.3\wlserver\common\derby\lib\derby.jar;E:\WEBLOG~1.3\wlserver\server\lib\xqrl.jar;E:\xiangmu\Mytest;C:\PROGRA~1\Java\JDK17~1.0_8\lib;C:\PROGRA~1\Java\JDK17~1.0_8\lib\tools.jar
.
PATH=;E:\WEBLOG~1.3\wlserver\server\native\win\x64;E:\WEBLOG~1.3\wlserver\server\bin;E:\WEBLOG~1.3\oracle_common\modules\org.apache.ant_1.9.2\bin;C:\PROGRA~1\Java\JDK17~1.0_8\jre\bin;C:\PROGRA~1\Java\JDK17~1.0_8\bin;C:\PROGRA~1\Java\JDK17~1.0_8\jre\bin\server;C:\PROGRA~1\Java\JDK17~1.0_8\jre\bin;C:\PROGRA~1\Java\JDK17~1.0_8\jre\lib\amd64;C:\oraclexe\app\oracle\product\112~1.0\server\bin;C:\Windows\System32;C:\Windows;C:\Windows\System32\wbem;C:\Windows\System32\WINDOW~1\v1.0\;C:\Windows\System32\OpenSSH\;E:\git\Git\cmd;E:\SVN\bin;C:\Users\ADMINI~1\AppData\Local\MICROS~3\WINDOW~1;E:\soft\maven\APACHE~1.9\bin;C:\PROGRA~1\MySQL\MYSQLS~1.7\bin;C:\PROGRA~1\Java\JDK17~1.0_8\bin;E:\git\Git\bin;E:\git\Git\usr\bin;E:\git\Git;C:\PROGRA~1\Java\JDK17~1.0_8\jre\bin;D:\zdcontomcat\zdc8\lo\program;E:\soft\eclipse\ECLIPS~1\eclipse;E:\WEBLOG~1.3\wlserver\server\native\win\x64\oci920_8
.
***************************************************
*  To start WebLogic Server, use a username and   *
*  password assigned to an admin-level user.  For *
.......

 

 补充:Runtime也可以获取系统的CPU数量

Runtime.getRuntime().availableProcessors()

 

调用cmd的时候中间的的/c与/k是cm的参数,windows下查看参数说明:/k参数可以执行完窗口停留

C:\Users\liqiang>cmd/?
启动 Windows 命令解释器的一个新实例

CMD [/A | /U] [/Q] [/D] [/E:ON | /E:OFF] [/F:ON | /F:OFF] [/V:ON | /V:OFF]
    [[/S] [/C | /K] string]

/C      执行字符串指定的命令然后终止
/K      执行字符串指定的命令但保留
/S      修改 /C 或 /K 之后的字符串处理(见下)
/Q      关闭回显
/D      禁止从注册表执行 AutoRun 命令(见下)
/A      使向管道或文件的内部命令输出成为 ANSI
/U      使向管道或文件的内部命令输出成为
        Unicode
/T:fg   设置前台/背景颜色(详细信息见 COLOR /?)
/E:ON   启用命令扩展(见下)
/E:OFF  禁用命令扩展(见下)
/F:ON   启用文件和目录名完成字符(见下)
/F:OFF  禁用文件和目录名完成字符(见下)
/V:ON   使用 ! 作为分隔符启用延迟的环境变量
        扩展。例如,/V:ON 会允许 !var! 在执行时
        扩展变量 var。var 语法会在输入时
        扩展变量,这与在一个 FOR
        循环内不同。
/V:OFF  禁用延迟的环境扩展。

注意,如果字符串加有引号,可以接受用命令分隔符 "&&"
分隔多个命令。另外,由于兼容性
原因,/X 与 /E:ON 相同,/Y 与 /E:OFF 相同,且 /R 与
/C 相同。任何其他开关都将被忽略。

如果指定了 /C 或 /K,则会将该开关之后的
命令行的剩余部分作为一个命令行处理,其中,会使用下列逻辑
处理引号(")字符:

    1.  如果符合下列所有条件,则会保留
        命令行上的引号字符:

        - 不带 /S 开关
        - 正好两个引号字符

 

补充:Runtime.getRuntime().addShutdownHook(thread); 方法 可以添加一个钩子,在Java 进程结束的时候会调用钩子中的线程。实际原理是最后一个非守护线程死亡的时候会开启DestroyJavaVM  调用相关方法,最终调用Runtime.getRuntime().addShutdownHook(thread); 的线程的start() 方法开启线程, 执行线程的任务。

例如: 测试在线程开始和结束的时候打印非守护线程信息。

public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        // 打印所有的线程
        printThread();
        System.out.println("====");

        Thread thread = new Thread(() -> {
            System.out.println("JVM 进程结束了 1~~~");
            printThread();
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("JVM 进程结束了 2~~~");
        });
        thread.setName("testName");

        Runtime.getRuntime().addShutdownHook(thread);

        System.out.println("111222");
    }

    private static void printThread() {
        System.out.println("====== 开始打印非守护线程");
        ThreadGroup currentGroup =
                Thread.currentThread().getThreadGroup();
        int noThreads = currentGroup.activeCount();
        Thread[] lstThreads = new Thread[noThreads];
        currentGroup.enumerate(lstThreads);
        for (int i = 0; i < noThreads; i++) {
            if (!lstThreads[i].isDaemon()) {
                System.out.println("线程号:" + i + " = " + lstThreads[i].getName());
            }
        }
        System.out.println("====== 结束打印非守护线程");
    }
}

  可以看到我们只创建了一个线程,然后调用Runtime.getRuntime().addShutdownHook(thread); 缓存起来。 我们并没有调用start方法开启线程。另外, 如果不想添加这样的钩子方法可以调用 Runtime.getRuntime().removeShutdownHook(thread); 进行移除。

结果:

====== 开始打印非守护线程
线程号:0 = main
====== 结束打印非守护线程
====
111222
JVM 进程结束了 1~~~
====== 开始打印非守护线程
线程号:0 = DestroyJavaVM
线程号:1 = testName
====== 结束打印非守护线程
JVM 进程结束了 2~~~

1. 原理查看:

(1) java.lang.Runtime#addShutdownHook

    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

(2) java.lang.ApplicationShutdownHooks#add

    private static IdentityHashMap<Thread, Thread> hooks;
    static {
        try {
            Shutdown.add(1 /* shutdown hook invocation order */,
                false /* not registered if shutdown in progress */,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            // application shutdown hooks cannot be added if
            // shutdown is in progress.
            hooks = null;
        }
    }

    static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }

        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

    static synchronized void add(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook.isAlive())
            throw new IllegalArgumentException("Hook already running");

        if (hooks.containsKey(hook))
            throw new IllegalArgumentException("Hook previously registered");

        hooks.put(hook, hook);
    }

  可以看到其核心设计是将线程放到了java.lang.ApplicationShutdownHooks#hooks 内部。 同时静态代码块调用 java.lang.Shutdown#add 添加一个任务

(3) java.lang.Shutdown 是一个重要的类,

package java.lang;

class Shutdown {

    /* Shutdown state */
    private static final int RUNNING = 0;
    private static final int HOOKS = 1;
    private static final int FINALIZERS = 2;
    private static int state = RUNNING;

    /* Should we run all finalizers upon exit? */
    private static boolean runFinalizersOnExit = false;

    // The system shutdown hooks are registered with a predefined slot.
    // The list of shutdown hooks is as follows:
    // (0) Console restore hook
    // (1) Application hooks
    // (2) DeleteOnExit hook
    private static final int MAX_SYSTEM_HOOKS = 10;
    private static final Runnable[] hooks = new Runnable[MAX_SYSTEM_HOOKS];

    // the index of the currently running shutdown hook to the hooks array
    private static int currentRunningHook = 0;

    /* The preceding static fields are protected by this lock */
    private static class Lock { };
    private static Object lock = new Lock();

    /* Lock object for the native halt method */
    private static Object haltLock = new Lock();

    /* Invoked by Runtime.runFinalizersOnExit */
    static void setRunFinalizersOnExit(boolean run) {
        synchronized (lock) {
            runFinalizersOnExit = run;
        }
    }


    /**
     * Add a new shutdown hook.  Checks the shutdown state and the hook itself,
     * but does not do any security checks.
     *
     * The registerShutdownInProgress parameter should be false except
     * registering the DeleteOnExitHook since the first file may
     * be added to the delete on exit list by the application shutdown
     * hooks.
     *
     * @params slot  the slot in the shutdown hook array, whose element
     *               will be invoked in order during shutdown
     * @params registerShutdownInProgress true to allow the hook
     *               to be registered even if the shutdown is in progress.
     * @params hook  the hook to be registered
     *
     * @throw IllegalStateException
     *        if registerShutdownInProgress is false and shutdown is in progress; or
     *        if registerShutdownInProgress is true and the shutdown process
     *           already passes the given slot
     */
    static void add(int slot, boolean registerShutdownInProgress, Runnable hook) {
        synchronized (lock) {
            if (hooks[slot] != null)
                throw new InternalError("Shutdown hook at slot " + slot + " already registered");

            if (!registerShutdownInProgress) {
                if (state > RUNNING)
                    throw new IllegalStateException("Shutdown in progress");
            } else {
                if (state > HOOKS || (state == HOOKS && slot <= currentRunningHook))
                    throw new IllegalStateException("Shutdown in progress");
            }

            hooks[slot] = hook;
        }
    }

    /* Run all registered shutdown hooks
     */
    private static void runHooks() {
        for (int i=0; i < MAX_SYSTEM_HOOKS; i++) {
            try {
                Runnable hook;
                synchronized (lock) {
                    // acquire the lock to make sure the hook registered during
                    // shutdown is visible here.
                    currentRunningHook = i;
                    hook = hooks[i];
                }
                if (hook != null) hook.run();
            } catch(Throwable t) {
                if (t instanceof ThreadDeath) {
                    ThreadDeath td = (ThreadDeath)t;
                    throw td;
                }
            }
        }
    }

    /* Notify the VM that it's time to halt. */
    static native void beforeHalt();

    /* The halt method is synchronized on the halt lock
     * to avoid corruption of the delete-on-shutdown file list.
     * It invokes the true native halt method.
     */
    static void halt(int status) {
        synchronized (haltLock) {
            halt0(status);
        }
    }

    static native void halt0(int status);

    /* Wormhole for invoking java.lang.ref.Finalizer.runAllFinalizers */
    private static native void runAllFinalizers();


    /* The actual shutdown sequence is defined here.
     *
     * If it weren't for runFinalizersOnExit, this would be simple -- we'd just
     * run the hooks and then halt.  Instead we need to keep track of whether
     * we're running hooks or finalizers.  In the latter case a finalizer could
     * invoke exit(1) to cause immediate termination, while in the former case
     * any further invocations of exit(n), for any n, simply stall.  Note that
     * if on-exit finalizers are enabled they're run iff the shutdown is
     * initiated by an exit(0); they're never run on exit(n) for n != 0 or in
     * response to SIGINT, SIGTERM, etc.
     */
    private static void sequence() {
        synchronized (lock) {
            /* Guard against the possibility of a daemon thread invoking exit
             * after DestroyJavaVM initiates the shutdown sequence
             */
            if (state != HOOKS) return;
        }
        runHooks();
        boolean rfoe;
        synchronized (lock) {
            state = FINALIZERS;
            rfoe = runFinalizersOnExit;
        }
        if (rfoe) runAllFinalizers();
    }


    /* Invoked by Runtime.exit, which does all the security checks.
     * Also invoked by handlers for system-provided termination events,
     * which should pass a nonzero status code.
     */
    static void exit(int status) {
        boolean runMoreFinalizers = false;
        synchronized (lock) {
            if (status != 0) runFinalizersOnExit = false;
            switch (state) {
            case RUNNING:       /* Initiate shutdown */
                state = HOOKS;
                break;
            case HOOKS:         /* Stall and halt */
                break;
            case FINALIZERS:
                if (status != 0) {
                    /* Halt immediately on nonzero status */
                    halt(status);
                } else {
                    /* Compatibility with old behavior:
                     * Run more finalizers and then halt
                     */
                    runMoreFinalizers = runFinalizersOnExit;
                }
                break;
            }
        }
        if (runMoreFinalizers) {
            runAllFinalizers();
            halt(status);
        }
        synchronized (Shutdown.class) {
            /* Synchronize on the class object, causing any other thread
             * that attempts to initiate shutdown to stall indefinitely
             */
            beforeHalt();
            sequence();
            halt(status);
        }
    }


    /* Invoked by the JNI DestroyJavaVM procedure when the last non-daemon
     * thread has finished.  Unlike the exit method, this method does not
     * actually halt the VM.
     */
    static void shutdown() {
        synchronized (lock) {
            switch (state) {
            case RUNNING:       /* Initiate shutdown */
                state = HOOKS;
                break;
            case HOOKS:         /* Stall and then return */
            case FINALIZERS:
                break;
            }
        }
        synchronized (Shutdown.class) {
            sequence();
        }
    }

}

可以看到:

(1) java.lang.Shutdown#shutdown 关闭方法会在JVM关闭最后一个非守护线程的时候进行调用, 由DestroyJavaVM 线程进程调用。方法内部根据状态进行判断然后调用下面(2) 。

(2) 然后调用java.lang.Shutdown#sequence 方法

  首先调用java.lang.Shutdown#runHooks。 这里实际就是同步调用hook.run() 方法。会调用到上面java.lang.ApplicationShutdownHooks 注册的一个hook, 进行调用 java.lang.ApplicationShutdownHooks#runHooks 方法跑我们添加到这里的 hook 线程, 这里是调用start() 方法开启线程(也就是启动我们Runtime.getRuntime().addShutdownHook(thread); 方法添加的线程)

 

  这个在Spring 容器中有使用,容器使用这个钩子实现容器销毁时候相关对象调用销毁方法 org.springframework.beans.factory.DisposableBean#destroy 方法。

 

注意: kill 信号能捕捉到,kill -9 的时候shutdown 添加的线程无效

(1) 测试代码:

import java.util.concurrent.CountDownLatch;

public class PlainTest {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("java.lang.Runtime.addShutdownHook");
        }));

        countDownLatch.await();
    }
}

测试结果:

/*******kill **/
[root@localhost test]# java PlainTest
java.lang.Runtime.addShutdownHook

/****kill -9 pid***/
[root@localhost test]# java PlainTest
Killed

(2) 可以自己捕捉一些信号做处理

1》 linux 查看支持的信号

[root@localhost test]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

2》编写信号处理代码

import sun.misc.Signal;
import sun.misc.SignalHandler;

import java.util.concurrent.CountDownLatch;


public class PlainTest implements SignalHandler {

    private void signalCallback(Signal sn) {
        System.out.println(sn.getName() + " is recevied.");
        // 退出程序
        System.exit(0);
    }

    @Override
    public void handle(Signal signalName) {
        signalCallback(signalName);
    }

    public static void main(String[] args) throws InterruptedException {
        PlainTest testSignalHandler = new PlainTest();
        // install signals
        Signal.handle(new Signal("TERM"), testSignalHandler);

        new CountDownLatch(1).await();
    }
}

3》 linux 环境下 kill 或者 kill -15 (等价) 相关进程,日志如下:

[root@localhost test]# java PlainTest
TERM is recevied.

 

posted @ 2018-01-05 18:19  QiaoZhi  阅读(22731)  评论(6编辑  收藏  举报