线程常用方法你了解吗(join、yield、interrupt)

前言:

理解线程常用方法,有助于我们对多线程有更深入的理解,你都知道哪些线程常用的方法,各自又有什么作用?本文将对线程 join、yield、interrupt 方法进行详解。

常用方法:

  • sleep:线程休眠。
  • wait:线程等待。
  • notify:单个线程唤醒。
  • notifyAll:唤醒所有线程。
  • join:线程强占。
  • yield:线程让步。
  • interrupt:线程打断。

线程常用方法你了解吗(sleep、wait、notify、notifyAll)

join 方法:

线程强占(谁调用 join 方法,谁就强占 cpu 资源,直到调用者执行结束),就是调用该方法的线程强占 cpu 时间片,join 方法 jdk 源码注释中描述的 等待该线程死亡,该线程值指的就是调用 join 方法的线程,也就是如果调用了 join 方法,那么 join 方法后面的代码,要等调用线程执行完毕后才可以得到执行。

源码简单分析:

public final void join() throws InterruptedException {
        join(0);
 }
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;
            }
        }
    }
  //判断线程状态 如果线程活着 就返回true 否则返回 false
  public final native boolean isAlive();

分析:根据源码我们可以知道 join 方法底层还是调用的 wait 方法,同时 join 方法中有一个 isAlive 方法,判断调用者线程是否存活,当调用者线程结束后,返回 false,while 循环结束(被强占 cpu 时间片的线程可以恢复执行了),注意这里的 wait(0),不是等待 0 秒,而是一直等待,直到调用 join 方法的线程执行结束,同时join 方法支持传入时间,这时候就会走另外一个分支,等待指定时间,join 方方法中的 wait 方法无需唤醒操作。

代码演示如下:

public class ThreadDemo extends Thread {


    public ThreadDemo() {
    }

    @Override
    public void run() {
        System.out.println("当前线程准备进入休眠,线程名称:" + Thread.currentThread().getName()+ ",当前时间:"+  System.currentTimeMillis() / 1000);
        try {
            //休眠 2 秒
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程休眠结束,线程名称:" + Thread.currentThread().getName()+ ",当前时间:"+  System.currentTimeMillis() / 1000);
    }


    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始运行,线程名称:" + Thread.currentThread().getName()+ ",当前时间:"+  System.currentTimeMillis() / 1000);
        ThreadDemo threadDemoOne = new ThreadDemo();
        threadDemoOne.start();
        //join 强占 cpu
        threadDemoOne.join();
        System.out.println("主线程结束运行,线程名称:" + Thread.currentThread().getName()+ ",当前时间:"+  System.currentTimeMillis() / 1000);
    }
}

没有使用 join 方法执行结果:

主线程开始运行,线程名称:main,当前时间:1711184107
主线程结束运行,线程名称:main,当前时间:1711184107
当前线程准备进入休眠,线程名称:Thread-0,当前时间:1711184107
当前线程休眠结束,线程名称:Thread-0,当前时间:1711184109

使用 join 方法执行结果:

主线程开始运行,线程名称:main,当前时间:1711184126
当前线程准备进入休眠,线程名称:Thread-0,当前时间:1711184126
当前线程休眠结束,线程名称:Thread-0,当前时间:1711184128
主线程结束运行,线程名称:main,当前时间:1711184128

使用 join 方法设置 join 时间的执行结果:

主线程开始运行,线程名称:main,当前时间:1711185453
当前线程准备进入休眠,线程名称:Thread-0,当前时间:1711185453
主线程结束运行,线程名称:main,当前时间:1711185454
当前线程休眠结束,线程名称:Thread-0,当前时间:1711185455

执行结果分析:注意观察执行结果中的时间(秒),根据执行结果可知,没有使用 join 时候,main 线程先执行结束,使用了 join,main 线程最后执行完,而设置了 join 时间的时候,我们发现时间到了,主线程就重新获得了 cpu 时间片,我们知道在很多情况下,都是通过主线程创建子线程的并启动子线程的,如果子线程中的业务需要比较长的时间,这时主线程往往会比子线程先结束,就会导致有时候主线程想获取子线程执行的业务结果,但是却获取不到,这个时候,就通过join方法来解决这个问题。

yield 方法:

线程让步,简单来说就是线程让出自己的 cpu 时间片,源码注释翻译为当前线程向调度程序申请让出对 cpu 处理器的使用,简单说就是主动放弃已经获取到的 cpu 时间片的执行权。

源码如下:

public static native void yield();

分析源码:

  • public static :静态方法,是 Thread 类的方法,可以通过类名调用。
  • native :yield 是一个 native 方法,简单理解就是操作系统的方法。
  • yield 方法没有异常抛出。
  • yield 方法结束后就转入就绪的状态。
  • yield 只会给优先级相同或者优先级更高的线程让步。

yield 方法效果测试:

场景设计:yield 方法既然是让出自己的 cpu 时间片,那如果做一个相同的操作,直接执行完和让出 cpu 时间片,小消耗的时间肯定不一样,按理论分析,不使用 yield 方法消耗的时间要小于使用 yield 方法消耗的时间,我们做如下测试。

测试代码:

public class ThreadDemo extends Thread {


    public ThreadDemo() {
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis() ;
        System.out.println("当前线程准备进入计算,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() );
        for (int a = 0; a < 1000000000; a++) {
            a = a + 1;
            //Thread.yield();
        }
        System.out.println("当前线程计算结束,线程名称:" + Thread.currentThread().getName() + ",消耗时间:" + (System.currentTimeMillis()  - start));
    }


    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始运行,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() );
        ThreadDemo threadDemoOne = new ThreadDemo();
        threadDemoOne.start();
        System.out.println("主线程结束运行,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() );
    }
}

没有使用 yield 方法测试结果:

主线程开始运行,线程名称:main,当前时间:1711187136105
主线程结束运行,线程名称:main,当前时间:1711187136106
当前线程准备进入计算,线程名称:Thread-0,当前时间:1711187136107
当前线程计算结束,线程名称:Thread-0,消耗时间:4

使用 yield 方法测试结果:

主线程开始运行,线程名称:main,当前时间:1711288301021
主线程结束运行,线程名称:main,当前时间:1711288301022
当前线程准备进入计算,线程名称:Thread-0,当前时间:1711288301022
当前线程计算结束,线程名称:Thread-0,消耗时间:37263

结果分析:
很明显使用 yield 方法,同样的逻辑处理,消耗的时间远大于使用了 yield 方法,证明 yield 方法生效,在实际的开发场景中,yield 方法的使用场景比较少。

interrupt 方法:

线程打断,打断调用者线程,但是 interrupt 方法和 stop 方法有本质区别,它并不会向 stop 方法一样直接中断一个线程的运行,调用 interrupt 方法的线程会继续运行下去,但是会设置一个中断标志 true,由调用者线程决定什么时候来来判执行线程中断,,,,,,,,,,,,,,,,,,。

使用 stop 方法中断线程会有什么问题?

使用 stop 方法虽然可以强行终止正在运行的线程,但使用 stop 方法是有隐患的,stop 方法杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁,这个后果就很严重了。

打断分类:

  • 打断正在运行的线程,interrupt 方法去打断正在运行线程时,被打断的线程会继续运行,但是该线程的打断标记会更新为true,可以根据打断标记来作为判断条件使得线程停止。
  • 打断处于阻塞状态的线程,interrupt 方法打断阻塞状态的线程时,会抛出异常,然后重置状态为 false,因此线程虽然被打断,但是打断标记依然为false。

打断正在运行的线程:

public class ThreadDemo extends Thread {


    public ThreadDemo() {
    }

    @Override
    public void run() {
        System.out.println("执行业务,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() / 1000);
        System.out.println("循环开始前打断标记为:" + Thread.currentThread().isInterrupted());
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("线程死循环了,线程中断标记:" + Thread.currentThread().isInterrupted());
        }
        System.out.println("循环结束后打断标记为:" + Thread.currentThread().isInterrupted());

    }


    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始运行,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis());
        ThreadDemo threadDemoOne = new ThreadDemo();
        threadDemoOne.start();
        //睡眠1毫秒 让线程跑一会儿
        Thread.sleep(1);
        //打断线程
        threadDemoOne.interrupt();
        System.out.println("主线程结束运行,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis());
    }
}

演示结果:

主线程开始运行,线程名称:main,当前时间:1711291731296
执行业务,线程名称:Thread-0,当前时间:1711291731
循环开始前打断标记为:false
线程死循环了,线程中断标记:false
线程死循环了,线程中断标记:false
。。。。。。
线程死循环了,线程中断标记:false
线程死循环了,线程中断标记:false
主线程结束运行,线程名称:main,当前时间:1711291731299
循环结束后打断标记为:true

结果分析:可以看到,我们启动子线程后,让主线程睡眠了 1 毫秒再执行子线程打断操作,结果也是如我们所料,还是现在执行了部分循环,后续自动停止了。

打断处于阻塞状态的线程:

public class ThreadDemo extends Thread {


    public ThreadDemo() {
    }

    @Override
    public void run() {
        System.out.println("执行业务,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis() / 1000);
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("线程死循环了,线程中断标记:" + Thread.currentThread().isInterrupted());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("循环结束后打断标记为:" + Thread.currentThread().isInterrupted());

    }


    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始运行,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis());
        ThreadDemo threadDemoOne = new ThreadDemo();
        threadDemoOne.start();
        //睡眠1毫秒 让线程跑一会儿
        Thread.sleep(1);
        //打断线程
        threadDemoOne.interrupt();
        System.out.println("主线程结束运行,线程名称:" + Thread.currentThread().getName() + ",当前时间:" + System.currentTimeMillis());
    }
}

演示结果:

在这里插入图片描述

结果分析:

  • 这里要使用 debugger 调试,才可以打印出为 true 的结果。
  • 可以很明显的看到,异常抛出之后,就无法获取到打断标志为 ture 的结果了,证实了抛出异常重置打断标注状态为 false。

终止线程的方式:

  • 线程运行完毕正常结束。
  • 共享变量结束线程,有些情况,线程需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程,我们使用某个变量,通过对变量值的判断来控制线程结束。
  • 上面讲到的使用 interrupt 方法结束线程。
  • 使用 stop 方法结束线程,不推荐使用,stop 方法结束线程有很大的风险,比如线程持有的锁没有被释放,被 stop 后会导致死锁发生。

如有错误的地方欢迎指出纠正。

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值