JVM系列(三十一) JVM调优实战-Jstack命令实战-线程方法使用

281 阅读7分钟

Jstack命令实战-线程方法使用

前面的文章,我们讲解了线程的6大状态及状态之间是如何切换的,这里面就涉及到了sleep,join等方法,到底这些方法该如何使用,我们为什么讲解线程方法的使用,因为我们要知道每个状态是如何产生的, 这样我们分析 jstack日志的时候,才能快速的定位问题

今天我们来讲解一下,线程方法的使用

1.线程方法

线程基本方法有很多,我们经常使用的大致分为以下几种

  • 1、public void start() 线程启动方法,线程状态从New变为Ready状态
  • 2、public void run() 线程的run方法是独立方法,如果独立执行,相当于main函数,方法执行
  • 3、public final void setName(String name) 设置参数,修改线程名称
  • 4、public final void setPriority(int piority) 设置线程的优先级,优先级1-10,1最低,5普通,10最高,系统根据优先级决定使用哪个线程
  • 5、public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程,在主线程关闭后无需手动关闭守护线程
  • 6、public final void join(long millisec) 等待该线程终止的时间,时长为 millis 毫秒,谁调用join方法,谁就强占cpu资源,直至执行结束
  • 7、public void interrupt() 中断线程
  • 8、public final boolean isAlive() 判断线程是否处于活动状态
  • 9、public static void static yield() 放弃CPU资源,暂停并执行其他线程。
  • 10、public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠指定时
  • 11、public static Thread currentThread() 返回对当前正在执行的线程对象的引用。

下面我们挑选几个常用的,或者比较容易混淆的,单独讲解

2.start 和 run的区别

Start()方法是线程启动方法,从New新建到Ready状态的转换

  • new 一个Thread,线程进入了新建New状态
  • 调用start() 方法,会启动一个线程并使线程进入了就绪Ready状态
  • 当分配到 CPU的时间片 后就可以开始运行了Runnable
  • start() 会执行线程的相应准备工作,然后 自动执行 run() 方法的内容,这是多线程工作

Run()方法是Thread的普通方法

  • 如果直接调用Run()方法,就像你写的test()方法一样,不会新开线程去工作
  • 在主线程中去执行,它会把run 方法当成一个main 线程下的普通方法去执行
  • 并不会新开线程,在新的线程中执行它,这并不是多线程工作

下面我们看下 start()方法的内部实现, start中有一个 start0()方法,该方法是native方法

private native void start0();

该方法并没有实现,是掉的底层C++的方法,native就是说明这个方法无法使用java平台来实现,是包装的底层本地方法

public synchronized void start() {
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

3.join 方法,到底是谁暂停,等待谁执行

3.1 无join方法,主线程先结束

join方法也是比较容易混淆的方法之一,为什么会有join方法?

  • join方法是为了防止 某个线程执行完结束的
  • 有些场景我们是需要 线程1 等待一会,等待线程2 线程结束,线程1再执行的
  • 这时候哦我们就需要join方法

那么到底是 谁join,谁等待谁完成,下面我们来验证一下,先说下结论, 谁join,等待谁,比如thread1.join,那么就是要等待 thread1执行完毕,才能执行下一步

我们看下 测试方法

  • Thread1 处理时间很长
  • //thread1.join(); 注释掉join,此时刻没有join方法
  • 主线程很快就执行完了,打印 主线程结束的日志
  • 子线程还在不停的慢慢执行
  • 如果主线程还有其他操作,依赖子线程的执行结果,这明显是不行的
  • 这时候就需要 子线程thread1.join 等待thread1执行完,再执行其他

代码如下:

package com.jzj.jvmtest.mythread;

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        //线程1 处理时间很长
        Thread thread1 = new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    Thread.sleep(1000);//子线程处理中
                    System.out.println("子线程-线程1 执行中");
                }

                System.out.println("子线程处理完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread1.start();

        //如果不适用 join方法,可能主线程很快就结束了,打印了日志
//        thread1.join();

        //使用了join方法,1 就是 thread1.join(), 谁掉join,等待谁,等待子线程1执行完 主线程再处理
        System.out.println("主线程结束");
    }
}

执行结果如下,先主线程结束,然后慢慢的子线程结束

image.png

3.2 执行join,thread1.join,等待thread1执行完毕

现在,我们修改代码,采用thread1.join方法,等待thread1执行完,再处理主线程场景

thread1.start();

//如果不适用 join方法,可能主线程很快就结束了,打印了日志
thread1.join();

//使用了join方法,1 就是 thread1.join(), 谁掉join,等待谁,等待子线程1执行完 主线程再处理
System.out.println("主线程结束");

执行结果如下

子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程-线程1 执行中
子线程处理完成
主线程结束

先执行子线程,主线程不会结束,一直等到 join,子线程执行完毕,主线程才执行,打印主线程结束 image.png

3.3 执行join(5000) 最多等待5000ms

我们看到 子线程执行了10s, 如果 join传时间参数呢?

  • join(5000) 5000ms 表示最多等待 5s, 之后再处理
  • join的时间就是sleep的时间
  • join后,原线程执行的就是wait,表示等待t1执行完后,才能继续执行

我们看到子线程执行了10s, 我们传参数join(5000), 应该是 5s后,主线程开始执行,下面我们验证下 修改代码

thread1.start();

//如果不适用 join方法,可能主线程很快就结束了,打印了日志
thread1.join(5000);

//使用了join方法,1 就是 thread1.join(), 谁掉join,等待谁,等待子线程1执行完 主线程再处理
System.out.println("主线程结束");

执行结果如下,最多等待 5s后,主线程就开始处理了

image.png

4.yield 和sleep 方法的区别

yield 和 sleep都会使当前线程暂停,那么他们之间的区别是什么?

yield :暂停当前正在执行的线程对象,并执行其他线程,它还是一定会执行其他线程还是说可能会执行其他线程,这就的看yield到底干了什么事情

  • yield 暂停当前线程运行状态,变为Ready就绪状态
  • 适用于相同优先级的线程间,适当的轮转
  • 释放CPU资源,以便让其他优先级相同的线程获得机会
  • 既然你和其他线程优先级相同,那么无法保证一定让步,因为你让步后,释放了cpu资源
  • 你和其他线程都是相同优先级,有可能你下次仍然被选中,再次恢复运行中Runable状态
  • 所以yield当前线程让步了,但是可能没让步成功,下次还是选中了它

我们再来看看sleep干了什么事, sleep 解释如下

  • sleep()方法暂停当前线程后,会给其他线程执行机会,不区分其他线程的优先级
  • sleep()方法会将线程转入阻塞Blocked状态,而不是 就绪Ready状态
  • sleep() 存在异常InterruptedException,,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常
  • 如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行

所以,他们俩还是有区别的,区别如下

  1. sleep没优先级 ,yield有优先级,给相同或者高优先就让步
  2. sleep进入阻塞blocked状态,yield进入就绪状态
  3. sleep不会被cpu立马选中,但是yield可以下一次立马被选中执行
  4. sleep会有中断异常InterruptedException声明,yield没有

至此,我们讲解了线程的一些基本方法,并且对方法做了对比,对比了start和run方法的区别,yield和sleep方法的区别及join方法的使用等,下一篇,我们介绍下jstack 如何定位并解决blocked及wait等待状态的问题