文章目录
一. 等待一个线程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
进行锁竞争的时候产生的阻塞(后面谈)
后续发现"某个线程"卡死了, 就需要关注线程的状态, 看看是在哪一行代码卡住了(阻塞)