[线程]等待一个线程, 获取当前线程引用,休眠当前线程, 线程的六种状态

一. 等待一个线程join

我们知道, 多个线程的调度顺序是无序的, 谁先被调度不确定, 谁先结束也不确定, 所以我们引入线程等待, 可以控制线程结束的先后顺序

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 3; i++){
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread end");
        });
        thread.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
        System.out.println("main end");
    }
}

上述代码的thread 和 main 之间的结束顺序是不确定的

如果我们希望让代码中的t先结束, main后结束, 就可以在main中使用线程等待join(join也要抛异常, 和sleep抛出的异常相同)
我们先修改一下代码, 让thread打印六次, main打印三次, 此时一定是main先结束, 再打印几次thread后, thread才结束

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 6; i++){
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread end");
        });
        thread.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
        System.out.println("main end");
    }
}

在这里插入图片描述
接下来我们加入join
注意: 在main线程中调用thread.join(), 就是让main等待t, 也就是等到t结束, main才能结束, 不然要在join处阻塞等待

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 6; i++){
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("thread end");
        });
        thread.start();
        for (int i = 0; i < 3; i++) {
            System.out.println("main");
            Thread.sleep(1000);
        }
        thread.join();
        System.out.println("main end");
    }
}

在这里插入图片描述
此时我们看到, main必须等到thread全部打印完毕并结束之后, main才能结束
总结一下:
1 main中调用join方法, 有两种可能:

  • 如果t线程已经结束了, 那么join此时立即返回, 不会涉及到阻塞
  • 如果t线程还没结束, 此时join就会阻塞等待, 等待t线程结束, join才能解除阻塞继续执行

2 主线程中同时调用多个join, 不管谁先谁后, 等待的总时间都是运行时间最长的那个
3 如果线程还没有start就被join, 那么join就会立刻返回, 不会抛出异常
4 其他线程也可以等待main线程

此外, join还有有参数的形式
1) void join(long millis)
传入的时间, 就是等待的最大时间, 单位是ms毫秒

public class Demo9 {
   public static void main(String[] args) throws InterruptedException {
       Thread thread = new Thread(() -> {
           for (int i = 0; i < 3; i++) {
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
           System.out.println("thread end");
       });
       thread.start();
       thread.join(1000);
       System.out.println("main end");
   }
}

上述代码中, thread线程需要3s才能结束, 但是main只等1s, 1s之后就会继续运行
在这里插入图片描述
但是如果等待join(5), 那么3s后, thread线程结束, main就继续执行了

2) void join(long millis, int nanos)
等待N毫秒ms, 等待M纳秒ns
但是纳秒这个级别的时间, 对于主流的操作系统来说, 太过精细, Windows / Linux 这样的系统, 无法精确到ns级别的时间, 甚至说到了ms级别都容易出现误差

1 s => 1000 ms毫秒
1ms => 1000 us微秒
1us => 1000 ns纳秒
1ns => 1000 ps皮秒

实际开发中,一般很少会使用死等的策略
比如, 现在有AB两个服务器, A中的主线程, 创建t线程, t线程给B发送请求, 紧接着就让主线程t.join, 等待t线程结束(获取B的响应), 此时如果B挂了, 如果是采用死等的策略, 就会使A中的主线程被卡住了, 无法继续执行后续逻辑了

二. 获取当前线程引用

在这里插入图片描述
在哪个线程中调用, 就会返回哪个线程的引用
在这里插入图片描述
上述代码, 在thread线程中调用Thread.currentThread(), 就会返回thread线程, 并赋给thread2

三. 休眠当前线程

在这里插入图片描述
这个方法我们已经不陌生了, 但是要注意的是, 使用sleep控制的是"线程休眠时间", 而不是"两个代码执行的间隔时间"
举例:
使用 System.currentTimeMillis() 方法可以获取到当前系统ms级时间戳
可以用来两个代码之间的执行间隔时间

   public static void main(String[] args) throws InterruptedException {
        System.out.println("beg : " + System.currentTimeMillis());
        Thread.sleep(1000);
        System.out.println("end : " + System.currentTimeMillis());
    }

在这里插入图片描述
结果我们发现, 线程休眠的时间是一秒, 但是代码间隔执行时间并不是正好1s, 是存在误差的
实际上, 具体的误差时间, 和运行环境, 机器的配置…都有关系, 但是间隔时间一定是>=sleep设定的时间的
此处设置sleep的时间, 是线程阻塞的时间, 1s之内, 线程是不会上cpu执行的(阻塞状态), 但是1s之后, 线程从阻塞状态恢复到就绪状态, 但不代表线程就会立刻去cpu上执行!

至于sleep内部的实现, 做了哪些事情, 我们是不知道的, java代码看不到
在这里插入图片描述
方法上带着native字样, 叫做"本地方法", 方法的实现, 实在JVM内部, 用c++代码实现的, 咱们用的Oracle官方的JDK不是开源的

四. 线程的状态

在java中, 对于线程的状态, 大概是分成6种不同的状态

1. NEW

Thread对象有了, 但是还没有start, 系统内部的线程还没有创建

public static void main(String[] args) {
        Thread thread = new Thread(() -> {

        });
        System.out.println(thread.getState());
        thread.start();
    }

结果:在这里插入图片描述

2. TERMINATED

线程已经终止了, 内核中的线程已经销毁了, Thread对象还在

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            
        });
        thread.start();
        thread.join();
        System.out.println(thread.getState());

    }

结果:在这里插入图片描述

3. RUNNABLE

就绪状态有两种形式:
1)这个线程正在cpu上执行
2)这个线程虽然没在cpu上执行, 但是随时可以调度到cpu上执行

 public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                System.out.println("hello");
            }

        });
        thread.start();
        System.out.println(thread.getState());
    }

结果:
在这里插入图片描述

4. WAITING

由于死等出现的阻塞状态

public static void main(String[] args) throws InterruptedException {
     Thread t = new Thread(() -> {
         while(true){
             System.out.println("hello");
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     });
     t.start();
     t.join();
 }

我们借助jconsole工具观察main的状态:
在这里插入图片描述

5. TIMED_WAITING

带有超时时间的阻塞状态

 public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(true){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        t.join(3600 * 1000);
    }

借助jconsole工具观察main的状态:
在这里插入图片描述
此时再观察一下thread的状态:
在这里插入图片描述
说明sleep也是TIMED_WAITING阻塞

6. BLOCKED

进行锁竞争的时候产生的阻塞(后面谈)

后续发现"某个线程"卡死了, 就需要关注线程的状态, 看看是在哪一行代码卡住了(阻塞)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值