>  기사  >  Java  >  모든 하위 스레드 작업이 완료된 후 메인 스레드를 종료하는 네 가지 방법

모든 하위 스레드 작업이 완료된 후 메인 스레드를 종료하는 네 가지 방법

坏嘻嘻
坏嘻嘻원래의
2018-09-14 16:27:434082검색

멀티스레딩은 Java에서 매우 중요한 지식입니다. 여기서는 Java 스레드 멀티스레딩을 마스터하는 데 매우 유용합니다.

    • 방법 1 Thread.sleep

    • 방법 2 ExecutorService

      #🎜 🎜#
    • 방법 3 thread.join

    • 방법 4 Thread.yield 및 Thread.activeCount

      #🎜 🎜#

코드를 작성하다가 이런 장면을 접했습니다. 각 하위 스레드의 실행 상태를 관찰할 수 없습니다. 처리가 수행되지 않으면 main 메서드가 완료된 후 다른 하위 스레드가 닫힙니다. 따라서 메인 스레드는 실행이 완료된 후에만 하위 스레드가 닫힐 때까지 기다려야 합니다. 과거에는 Thread.sleep(time)을 추가하는 것이 더 엉성한 접근 방식이었습니다. 그러나 이 방법은 수작업이 필요하기 때문에 완벽하지 않습니다. 따라서 이를 달성하기 위한 몇 가지 방법은 다음과 같습니다. 목적.

방법 1 Thread.sleep

#🎜🎜 #이 방법은 가장 일반적인 방법이기는 하지만 요구 사항을 거의 충족할 수 없습니다.

public static void main(String[] args) throws InterruptedException{        for(int i=0; i<10; i++){            new Thread("subthread" + i){
                @Override                public void run() {                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "finished");
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }
        Thread.sleep(2000);
        System.out.println("main thread finished");
    }
main 方法运行完毕后其他子线程都会关闭, 无法观察到所有子线程的详细的运行情况, 于是需要让主线程等待所有子线程运行完毕后才关闭, 以前比较马虎的做法就是在main 函数里面添加Thread.sleep(time) 但是这种方法始终不完美, 因为需要人为的设置等待时间, 不是最佳的实践, 于是查阅了一些资料以及博客, 此处总结一下实现这个目的的四种方法.

方法一 Thread.sleep

这应该是最常见的方法, 虽然不是最佳的实践, 但是可以勉强满足需求.

subthread0finished
subthread1finished
subthread2finished
subthread6finished
subthread4finished
subthread5finished
subthread3finished
subthread9finished
subthread8finished
subthread7finished
main thread finished

运行结果:

public static void main(String[] args) {        // 创建一个ExecutorService
        ExecutorService ex =  Executors.newCachedThreadPool();        for(int i=0; i<10; i++){            // 添加 task
            ex.submit(new Thread("subthread" + i){

                @Override                public void run() {                    try{
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "finished");
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }        // 使用shutdown 会通知executorservice 停止添加其它task 它会等待所有子线程运行结束才退出Java进程
        ex.shutdown();
        System.out.println("main thread finished");

    }

方法二 ExecutorService

可以使用线程池实现, 常用的线程池对象都是ExecutorService 接口的实现, 其中提供了shutdown 等方法可以保证当前提交的任务在子线程上运行完毕后 Java进程正常退出.

main thread finished
pool-1-thread-3finished
pool-1-thread-4finished
pool-1-thread-2finished
pool-1-thread-1finished
pool-1-thread-5finished
pool-1-thread-7finished
pool-1-thread-8finished
pool-1-thread-6finished
pool-1-thread-9finished
pool-1-thread-10finished

运行结果为:

public static void main(String[] args) {        // 创建一个ExecutorService
        ExecutorService ex =  Executors.newCachedThreadPool();        for(int i=0; i<10; i++){            // 添加 task
            ex.submit(new Thread("subthread" + i){

                @Override                public void run() {                    try{
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "finished");
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            });
        }        // 使用shutdown 会通知executorservice 停止添加其它task 它会等待所有子线程运行结束才退出Java进程
        ex.shutdown();        try {            // 设置等待时间等待子线程运行完毕
            if(!ex.awaitTermination(2000, TimeUnit.MILLISECONDS)){                // 等待时间内子线程并未全部运行完毕就直接关闭
                ex.shutdownNow();
            }
        }catch(InterruptedException e){
            ex.shutdownNow();
        }

        System.out.println("main thread finished");

    }

此种方法有一些小的瑕疵, 我们从输出的信息可以看到主线程其实先于子线程运行完毕, 所以这种方法只能够保证子线程在程序退出之前可以运行完, 但是不能够保证主线程在子线程运行完毕之后再执行.所以代码还需要更改, 添加一个awaitTermination(time, timeunit) 设置一个较为合理的等待的时间, 等待子线程运行完毕.

pool-1-thread-1finished
pool-1-thread-5finished
pool-1-thread-4finished
pool-1-thread-9finished
pool-1-thread-8finished
pool-1-thread-3finished
pool-1-thread-2finished
pool-1-thread-7finished
pool-1-thread-6finished
pool-1-thread-10finished
main thread finished

运行结果:

public static void main(String[] args) throws InterruptedException{
        List<Thread> list = new ArrayList<>();        for(int i=0; i<10; i++){
            Thread t = new Thread("subthread" + i){
                @Override                public void run() {                    try{
                        Thread.sleep(3000);
                        System.out.println(Thread.currentThread().getName() + "finished");
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            };
            list.add(t);
            t.start();
        }        for(Thread item : list){
            item.join();
        }
        System.out.println("main thread finished");
    }

可以看到主线程执行的内容在最后输出的. 这个方法与方法一一样都需要设置等待时间, 不是很完美的方法.

方法三 thread.join

thread.join 表示运行这段代码的线程会处于挂起状态, 等待调用这个方法的线程(此处就是这个thread) 运行完毕后才继续运行. 下面的例子中子线程都是在main 线程上面创建的, 所以在main 线程里面运行某一个子线程.join 时会等待子线程运行完毕才继续运行main 线程, 代码如下:

subthread1finished
subthread2finished
subthread0finished
subthread3finished
subthread6finished
subthread5finished
subthread4finished
subthread9finished
subthread7finished
subthread8finished
main thread finished

运行结果:

public static void main(String[] args) {        // 关键参数
        int defaultThreadNum = 2;        for(int i=0; i<10 ;i++){            new Thread("subthread" + i){
                @Override                public void run() {                    try {
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName() + "finished");
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }.start();
        }        while(Thread.activeCount() > defaultThreadNum){            // 当活跃线程数大于设定的默认线程数的时候 主线程让步
            Thread.yield();
        }
        System.out.println("main thread finished");
    }

使用这个方法相比去上面两种方法优点在于不用设置等待时间.

方法四 Thread.yield and Thread.activeCount

首先说明一下这两个方法的作用, Thread.yield 通俗讲就是让步, 调用这个方法的当前线程放弃 自己占用CPU 的权利. 但是并不表示当前线程一定不再执行. Thread.activeCount 方法返回的是当前调用这个线程对应的线程组中所有的活跃线程数量. 在创建线程的时候(new Thread) 其中这个ThreadGroup 参数指的就是创建线程对应的线程组, 如果这个参数没有指定, 那么创建的线程与创建这个线程的线程是同一个线程组. 代码如下:

subthread0finished
subthread4finished
subthread1finished
subthread8finished
subthread9finished
subthread5finished
subthread2finished
subthread3finished
subthread6finished
subthread7finished
main thread finished

运行结果:

public static void main(String[] args) {
        Thread.currentThread().getThreadGroup().list();
    }

有一个很关键的地方就是这个defaultthreadnum 设置的是2. 有一些blog中设置的是1, 但是1会导致无限循环, 主线程无法退出, 原因在于大家都认为主线程所在的线程组中排除子线程后只剩主线程一个线程了, 其实不然, 比如我们运行如下的代码:

java.lang.ThreadGroup[name=main,maxpri=10]    Thread[main,5,main]    Thread[Monitor Ctrl-Break,5,main]

输出:

rrreee

可以看到主线程所在的线程组中还有一个叫Monitor Ctrl-Break 的线程, 因此排除所有子线程后还剩余2个线程, 所以循环判断的门限(defaultThreadNum실행 결과:
rrreee

방법 2 ExecutorService

# 🎜🎜# 스레드 풀을 사용하여 구현할 수 있습니다. 일반적으로 사용되는 스레드 풀 개체는 ExecutorService의 구현입니다. code> 인터페이스는 현재 제출된 작업이 하위 스레드에 있는지 확인하기 위해 <code>shutdown과 같은 메소드를 제공하며, 작업이 완료된 후 Java 프로세스가 정상적으로 종료됩니다. #실행 결과는 다음과 같습니다. rrreee

이 방법에는 몇 가지 사소한 결함이 있습니다. 출력 정보에서 메인 스레드를 볼 수 있습니다. 실제로 하위 스레드보다 먼저 실행이 완료되므로 이 방법은 다음을 보장할 수 있습니다. 프로그램이 종료되기 전에 하위 스레드의 실행이 완료될 수 있지만 하위 스레드의 실행이 완료된 후 메인 스레드가 실행된다는 보장은 없습니다. 따라서 코드를 계속 변경해야 합니다. awaitTermination(time, timeunit) 보다 합리적인 대기 시간을 설정하고 하위 스레드 실행이 완료될 때까지 기다립니다.

rrreee

실행 결과:

rrreee실행된 내용을 볼 수 있습니다. 이 방법은 첫 번째 방법과 마찬가지로 대기 시간을 설정해야 하는데, 이는 완벽한 방법은 아닙니다.

방법 3 thread.join

thread.join은 이 코드를 실행하는 스레드가 일시 중단된 상태에 있으며, 이 메서드를 호출하는 스레드(여기서는 이 스레드)가 실행을 계속하기 전에 완료될 때까지 기다리고 있음을 의미합니다. 스레드는 메인 스레드에서 생성되므로 메인 스레드에서 sub-thread.join을 실행할 때 실행을 계속하기 전에 하위 스레드 실행을 완료하세요. main 스레드의 경우 코드는 다음과 같습니다.

rrreee
실행 결과:

rrreee

장점 위의 두 가지 방법에 비해 이 방법을 사용하면 대기 시간을 설정할 필요가 없다는 점입니다.

방법 4 Thread.yield 및 Thread.activeCount

우선, 이 두 메서드의 기능을 설명하겠습니다. Thread.yield 일반 용어로 말하면 이 메서드를 호출하는 현재 스레드가 점유권을 <code>포기합니다. 그러나 이는 현재 스레드가 더 이상 실행되지 않는다는 의미는 아닙니다. Thread.activeCount 메서드는 현재 호출 스레드에 해당하는 스레드 그룹의 활성 스레드 수를 반환합니다. 스레드(새 스레드)를 생성할 때 ThreadGroup 매개변수는 생성된 스레드에 해당하는 스레드 그룹을 참조합니다. 이 매개변수를 지정하지 않으면 생성된 스레드는 동일한 스레드 그룹에 속합니다. 이 스레드를 생성한 스레드입니다.

rrreee

실행 결과: rrreee한 가지 중요한 점은 이 defaultthreadnum이 다음으로 설정되어 있다는 것입니다. 2. 일부 블로그에서는 1로 설정하지만 1이면 무한 루프가 발생하여 메인 스레드가 종료되지 않습니다. 그 이유는 모든 사람들이 메인 스레드가 위치한 스레드 그룹이 하위 스레드를 제외한다고 생각하기 때문입니다. 실제로는 그렇지 않습니다. 예를 들어 다음 코드를 실행하면

rrreee#🎜🎜#Output: #🎜🎜#rrreee#🎜🎜#여기에서 확인할 수 있습니다. 메인 스레드가 위치한 스레드 그룹에는 다른 스레드가 있는데 Monitor Ctrl-Break라는 스레드가 있어서 모든 하위 스레드를 제외하고도 여전히 2개의 스레드가 남아 있으므로 루프에 대한 임계값은 다음과 같습니다. 판단(defaultThreadNum)을 2로 설정해야 합니다. # 🎜🎜#이 방법도 대기 시간 설정이 필요하지 않습니다.#🎜🎜##🎜🎜#요약하면, 모든 하위 스레드의 실행이 완료된 후 메인 스레드를 실행하려면 방법 3과 방법 4를 사용할 수 있습니다.#🎜 🎜##🎜🎜# #🎜🎜##🎜🎜##🎜🎜# #🎜🎜##🎜🎜#관련 권장 사항: #🎜🎜##🎜🎜##🎜🎜##🎜🎜#Java 스레드 멀티 스레드 종합 분석#🎜🎜# # 🎜🎜##🎜🎜##🎜🎜#php5 비스레드 안전과 스레드 안전의 차이점#🎜🎜##🎜🎜#

위 내용은 모든 하위 스레드 작업이 완료된 후 메인 스레드를 종료하는 네 가지 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.