Java多线程初学者看过来,这个坑别踩

184 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

下面这段代码,创建了两个线程,t1睡眠3s再输出,t2等待t1执行完毕再输出

@Test
public void _join() {
    Thread t1 = new Thread(()->{
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("t1");
    });
    Thread t2 = new Thread(()->{
        try {
            t1.join();
            System.out.println("t2");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
      
    });
    t2.start();
    t1.start();
}

预期输出是不是应该是这样?

t1
t2

Process finished with exit code 0

但其实输出结果是这样


Process finished with exit code 0

我们来分析一下,用户线程随着主线程结束只有一种情况

  • 用户线程被偷偷设置为了守护线程,我们来打印一下
...
t2.start();
t1.start();
System.out.println("t1 t2你们是Daemon吗?"+t1.isDaemon()+' '+t2.isDaemon());
t1 t2你们是Daemon吗?false false

Process finished with exit code 0

答案是没有被设置为用户线程

那又是什么原因呢?会不会是主线程结束太快了还没来得及启动子线程? 我们来给主线程加一个Sleep()试试

...
t2.start();
t1.start();
Thread.sleep(1000);

结果是...


Process finished with exit code 0

这会我已经抓狂了,说好用户线程不会随主线程结束而结束呢??为什么别人的demo能跑,我的不能呢?这时候就使用排除法,首先排除我长得帅这个原因,那就是你了,Junit4.@Test, 于是把测试方法换成main方法,程序就正常执行了

为什么呢?我们给方法打上断点,开始DEBUG

直接将调用栈拉到最后,发现JUnitCore类中有一个run方法(再往下的方法进不去,不在classpath内,是插件),往上滑发现了Junitmain方法,如下图

image.png

Result result = (new JunitCore()).runMain(new RealSystem(),args);
System.exit(result.wasSuccessful()?0:1);

分析上面Main函数的代码得知,无论程序执行的结果是什么,Junit都会直接退出JVM,所以这就造成了我们子线程还在睡觉,家被拆了的事实

若我们需要通过@Test注解使用多线程,则需要使用能阻塞主线程的方法,因为不阻塞主线程,Junit就直接把你JVM干没了 比如可以这样做

  • 使用join()方法阻塞主线程
...
t2.start();
t1.start();
t1.join();
t2.join();
  • 使用CountDownLatch等等