主线程退出对于子线程的影响

1,038 阅读2分钟

先上结论:主线程退出对子线程是没有影响的,因为线程本质上是平等的,并不存在'父子关系'。

参考文章:主线程退出对子线程的影响

代码示例

主线程退出不影响子线程


public class ThreadTest {
    Map<String, Future<?>> futureMap = new HashMap<>();
    
    @Test
    public void test() throws InterruptedException {
        ThreadMain threadMain = new ThreadMain();
        Future<?> future = ThreadUtil.execAsync(threadMain);
        futureMap.put(threadMain.getTHREAD_NAME(), future);

        do {
            futureMap.forEach((key, value) -> {
                if (value.isDone()) {
                    System.out.println("Future " + key + " isDone: " + value.isDone());
                }
            });
            futureMap.entrySet().removeIf(item -> item.getValue().isDone());
            TimeUnit.SECONDS.sleep(1);

        } while (!futureMap.isEmpty());

        System.out.println("all done");
    }

    @Getter
    public class ThreadMain extends Thread {
        private final String THREAD_NAME = "zk--TheadMain";

        @SneakyThrows
        @Override
        public void run() {
            Thread.currentThread().setName(THREAD_NAME);
            System.out.println(THREAD_NAME + " - start" );

            Thread1 thread1 = new Thread1();
            Future<?> future1 = ThreadUtil.execAsync(thread1);
            futureMap.put(thread1.getTHREAD_NAME(), future1);

            Thread2 thread2 = new Thread2();
            Future<?> future2 = ThreadUtil.execAsync(thread2);
            futureMap.put(thread2.getTHREAD_NAME(), future2);

            TimeUnit.SECONDS.sleep(5);
            System.out.println("=====" + THREAD_NAME + " - Done");
        }
    }

    @Getter
    public class Thread1 extends Thread {
        private final String THREAD_NAME = "zk--Thread1";

        @Override
        @SneakyThrows
        public void run() {
            Thread.currentThread().setName(THREAD_NAME);
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(THREAD_NAME + " - " + i);
            }

            System.out.println("=====" + THREAD_NAME + " - Done");
        }
    }

    @Getter
    public class Thread2 extends Thread {
        private final String THREAD_NAME = "zk--Thread2";

        @Override
        @SneakyThrows
        public void run() {
            Thread.currentThread().setName(THREAD_NAME);
            for (int i = 0; i < 15; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(THREAD_NAME + " - " + i);
            }

            System.out.println("=====" + THREAD_NAME + " - Done");
        }
    }
}

输出结果:

zk--TheadMain - start
zk--Thread1 - 0
zk--Thread2 - 0
zk--Thread1 - 1
zk--Thread2 - 1
zk--Thread1 - 2
zk--Thread2 - 2
zk--Thread2 - 3
zk--Thread1 - 3
=====zk--TheadMain - Done
zk--Thread2 - 4
zk--Thread1 - 4
Future zk--TheadMain isDone: true
zk--Thread2 - 5
zk--Thread1 - 5
zk--Thread1 - 6
zk--Thread2 - 6
zk--Thread1 - 7
zk--Thread2 - 7
zk--Thread1 - 8
zk--Thread2 - 8
zk--Thread2 - 9
zk--Thread1 - 9
=====zk--Thread1 - Done
zk--Thread2 - 10
Future zk--Thread1 isDone: true
zk--Thread2 - 11
zk--Thread2 - 12
zk--Thread2 - 13
zk--Thread2 - 14
=====zk--Thread2 - Done
Future zk--Thread2 isDone: true
all done

Process finished with exit code 0

可以看出主线程 TheadMain 在结束后,子线程Thread1Thread2依然在继续执行。

让主线程等待子线程结束

使用 join 阻塞调用者

代码示例


public class ThreadTest2 {
 
    @Test
    public void test() throws InterruptedException {
        Thread t1 = new ThreadA();
        Thread t2 = new ThreadB();

        t1.start();
        t1.join();
        t2.start();

        System.out.println("我是主线程");
        TimeUnit.SECONDS.sleep(5);
    }

    public class ThreadA extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            Thread.sleep(3000);
            System.out.println("线程A执行");
        }
    }

    public class ThreadB extends Thread {
        @SneakyThrows
        @Override
        public void run() {
            Thread.sleep(2000);
            System.out.println("线程B执行");
        }
    }

}

执行结果: image.png 原理是 Thread.join()方法会阻塞调用者,从而阻塞主线程。

使用 Feature 来判断子线程是否结束

    @Test
    public void test2() throws InterruptedException {

        Thread1 thread1 = new Thread1();
        Future<?> future1 = ThreadUtil.execAsync(thread1);
        futureMap.put(thread1.getTHREAD_NAME(), future1);

        Thread2 thread2 = new Thread2();
        Future<?> future2 = ThreadUtil.execAsync(thread2);
        futureMap.put(thread2.getTHREAD_NAME(), future2);
        
        for (; ; ) {
            TimeUnit.SECONDS.sleep(1);
            if (futureMap.values().stream().allMatch(Future::isDone)) {
                break;
            }
        }

        System.out.println("all done");
    }

需要注意的是:Feature.isDone() 在线程正常结束、出现异常、被取消时都会返回 True

使用线程工具做判断

可以使用 CountDownLatch、CyclicBarrier、Semaphore 等工具做判断. 用法示例如下:

CountDownLatch

/**
   * 模拟 主线程 依赖 线程A初始化一个数据,才能继续加载后续逻辑
   */
public static void main(String[] args) throws InterruptedException {
    AtomicReference<String> key = new AtomicReference<>("");
    CountDownLatch countDownLatch = new CountDownLatch(3);
    Thread t = new Thread(() -> {
        try {
            //休眠5秒,模拟数据的初始化
            TimeUnit.SECONDS.sleep(5);

            key.set("核心秘钥123456");
            System.out.println("数据1初始化完毕");

            //释放---此处可以在任何位置调用,很灵活
            countDownLatch.countDown();

            System.out.println("数据2初始化完毕");
            countDownLatch.countDown();

            System.out.println("数据3初始化完毕");
            //countDownLatch.countDown();

         } catch (InterruptedException e) {
             e.printStackTrace();
         }

    });
    t.start();

    //等待数据初始化,阻塞
    //只有当count=0时,await后面的代码才会执行
    countDownLatch.await();
    System.out.println("key:" + key.get());
}

CyclicBarrier

CyclicBarriercyclicBarrier= new CyclicBarrier(4, this);
cyclicBarrier.await();

Semaphore信号量

Semaphore semaphore= new Semaphore(5);
semaphore.acquire();
semaphore.release();

CAS(自旋锁)

//自旋锁
public class SpinLock {

  private AtomicReference<Thread> sign = new AtomicReference<>();

  public void lock(){
    Thread current = Thread.currentThread();
    while(!sign .compareAndSet(null, current)){
    }
  }

  public void unlock (){
    Thread current = Thread.currentThread();
    sign .compareAndSet(current, null);
  }
}