详解Java线程中的join()方法

对于Java中的join()方法的描述,我们首先来看下源码当中的解释

在这里插入图片描述

从源码当中的描述,我们只能知道join方法的作用是让线程陷入等待。其中可以传递以毫秒为单位的等待时间参数,如果传递参数为0,则线程会一直等待。

其实对于join方法,网上有很多解释,大都是只说如何使用,并没有对join当中的实现进行分析。
因此,在此结合网上的各种说法,对线程中的join方法进行源码分析,同时也记录自己的学习过程。

首先先来了解join方法如何使用,以及它的作用。
为了更有对比性的展示,首先来个简单并正常使用(不使用join)情况的多线程程序:

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Counter counter = new Counter();

        Thread tA = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.printA();
            }
        });

        Thread tB = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.printB();
            }
        });

        Thread tC = new Thread(new Runnable() {
            @Override
            public void run() {
                counter.printC();
            }
        });

        tA.start();
        tB.start();
        tC.start();
    }

    static class Counter {
        
        public void printA() {
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("A");
            }
        }

        public void printB() {
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("B");
            }
        }

        public void printC() {
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("C");
            }
        }
    }
}

这个程序只是创建了三个线程,然后进行调用。程序结果如下:

在这里插入图片描述

可以看到,结果是三个线程都在执行自己的方法,各自线程互不干扰。

那么在此基础上,在调用tA.start()之后,再调用join(),再看运行结果:

在这里插入图片描述
在这里插入图片描述

可以看到,tA.start()当中的内容都执行完后,才轮到后面的tB和tC线程执行。
这个结果便是网上最常说的 “t.join()方法会使所有线程都暂停并等待t的执行完毕后再执行

但如果看过源码就知道,这种说法是十分片面的

请看join的源码:

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

通过源码了解,join之所以可以实现线程等待是因为调用wait方法。

那么对于线程不够了解的朋友就可能会问:在本文的示例中是tA.join(),而join中又调用了wait。那么不是让tA执行的线程陷入等待了吗,那不就是和网上的说法“让tA线程执行完,再执行其他线程”完全不一致了?

如果你有这样的疑问的话,说明你对wait方法也不够了解。
来看下wait方法的源码注释解释:

在这里插入图片描述

从wait方法的方法注释可以看到,wait方法会让当前线程陷入等待。注意,是当前线程

那么我们再回头看下示例,在执行tA.join()这句代码的时候,当前线程是谁?
没错,是main主线程,而不是tA执行的线程。
tA执行的线程和main线程同时都在执行,而调用执行A.join()这句代码确确实实是在main主线程当中执行的。

因此在tA.join()当中的wait(0)方法是让main线程陷入了无尽的等待中。正是因为如此,在tA.join()之前的代码都会正常从上往下执行,而在tA.join()之后的代码都随着main线程陷入等待而无法继续执行。这样便达到了网上说的 “t.join()方法会使所有线程都暂停并等待t的执行完毕后再执行”。

到此,关于join的讲解尚未结束。之前有说到,join方法中调用wait(0)让当前线程陷入无尽的等待。那么有wait等待就会有相应的notify或者notifyAll唤醒,那么唤醒的地方又在哪里?

这个问题我也跟踪过join的方法,但始终发现不了在哪里唤醒等待的线程。所以在此引用网上的结论:join源码中,只会调用wait方法,并没有在结束时调用notify,这是因为线程在die的时候会自动调用自身的notifyAll方法,来释放所有因为该锁陷入等待的资源和锁

#thread.cpp
//调用join后,是在JVM当中调用该方法自动唤醒线程
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
  ...
  // 这个方法入口
  ensure_join(this);
  ...
}

static void ensure_join(JavaThread* thread) {
  ...
  ObjectLocker lock(threadObj, thread);
  ...
  //唤醒等待在thread对象上的线程
  lock.notify_all(thread);
  ...
}

#ObjectSynchronizer.hpp
void notify_all(TRAPS)      { 
  ObjectSynchronizer::notifyall(_obj, CHECK); 
}

---------------------
作者:MrFC
来源:CSDN
原文:https://blog.csdn.net/weixin_41083377/article/details/114598071

  • 76
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: nextLine方法JavaScanner类的一个方法,用于读取输入流的下一行文本。它会读取输入流的文本直到遇到换行符("\n")或回车符("\r")为止,并返回这一行的文本内容。 在使用nextLine方法时,需要注意以下几点: 1. nextLine方法会读取输入流的换行符或回车符,因此如果在使用其他Scanner方法(如nextInt、nextDouble等)时输入了换行符或回车符,需要先使用nextLine方法将其读取掉。 2. 如果输入流没有下一行文本,nextLine方法会阻塞程序,直到有新的文本输入或输入流关闭。 3. nextLine方法返回的文本包括换行符或回车符,因此需要根据具体情况进行处理。 总之,nextLine方法是一个非常常用的方法,可以方便地读取输入流的文本,适用于各种输入场景。 ### 回答2: Java的nextLine()方法是Scanner类方法。它可以读取用户输入的一行数据,直到输入回车结束。 当一个程序需要读取用户的输入时,可能需要使用nextLine()方法。该方法的返回值是一个字符串,包含用户输入的整行文本。下面是使用nextLine()方法的示例代码: Scanner scanner = new Scanner(System.in); System.out.println("请输入一行文字:"); String line = scanner.nextLine(); System.out.println("您输入的文字是:" + line); 以上代码,Scanner类的实例scanner读取用户输入的一行文本,存储在字符串line,然后将其输出到控制台。 需要注意的是,在使用Scanner类的nextLine()方法时,必须先调用Scanner类的next()方法,然后再调用nextLine()方法。这是由于next()方法只会读取空格之前的数据,而nextLine()方法则会读取换行符之前的所有数据。如果不先调用next()方法,会出现问题,因为nextLine()方法会读取上一次next()方法未读取的数据。 另外,当读取用户输入时,需要注意输入流的关闭问题。如果输入流没有关闭,程序会一直等待用户输入,直到输入流关闭为止。如果用户想要结束输入,可以在控制台输入Ctrl + Z(Windows系统下)或Ctrl + D(Mac、Linux系统下)。 总之,Java的nextLine()方法可以读取用户输入的一行文本,是编程时常用的方法之一。在使用时需要注意输入流的关闭问题和先调用Scanner类的next()方法。 ### 回答3: Java 的 nextLine 方法是从 Scanner 类继承而来的,它的主要作用是读取输入流的下一行文本。当遇到换行符或者输入流结束时,该方法将会返回当前行的内容并忽略换行符。在 Java ,nextLine 是 Scanner 类最为常用的方法之一,它可以读取用户输入的一行文本,同时还可以获取整个输入流包括空格的文本内容。 nextLine 方法的语法格式如下: public String nextLine() 在使用该方法时,需要通过实例化 Scanner 类的对象来调用其 nextLine 方法,例如: import java.util.Scanner; public class Example { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String str = scanner.nextLine(); System.out.println("输入的内容是:" + str); scanner.close(); } } 在这个示例,我们创建了一个 Scanner 对象,并使用 nextLine 方法从标准输入流读取了一行用户输入,然后将输入内容输出至控制台。 需要注意的是,nextLine 方法只能读取到该行的最后一个换行符,如果没有换行符,该方法将会处于等待状态,直到输入流出现换行符或输入流结束。此外,由于 nextLine 方法会读取到当前行的换行符,因此在连续使用 next 或 nextLine 方法时,需要特别注意会出现计算机无法输入字符的情况,这时可以使用 next 方法来过滤掉空格和其他不可见字符,从而避免上述问题。 总之,nextLine 方法Java 常用的输入方法之一,它可以读取输入流的一整行文本,并忽略换行符。在使用该方法时需要注意输入流的结束情况,避免因输入流没有结束而导致进程挂起。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值