先上结论:主线程退出对子线程是没有影响的,因为线程本质上是平等的,并不存在'父子关系'。
参考文章:主线程退出对子线程的影响
代码示例
主线程退出不影响子线程
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在结束后,子线程Thread1、Thread2依然在继续执行。
让主线程等待子线程结束
使用 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执行");
}
}
}
执行结果:
原理是
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);
}
}