[线程]等待一个线程, 获取当前线程引用,休眠当前线程, 线程的六种状态
文章目录
- 一. 等待一个线程join
- 二. 获取当前线程引用
- 三. 休眠当前线程
- 四. 线程的状态
- 1. NEW
- 2. TERMINATED
- 3. RUNNABLE
- 4. WAITING
- 5. TIMED_WAITING
- 6. BLOCKED
一. 等待一个线程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
进行锁竞争的时候产生的阻塞(后面谈)
后续发现"某个线程"卡死了, 就需要关注线程的状态, 看看是在哪一行代码卡住了(阻塞)