等待一个线程 - join()
多个线程之间发生并发执行,会发生随机调度,此时我们想控制线程结束的先后顺序,可以通过使用join()
虽然我们可以通过sleep休眠的时间来控制结束的顺序,但是这种情况并不科学
join()
java
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("hello thred");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t线程结束");
});
t.start();
t.join();
System.out.println("main线程结束");
}

在main线程中,调用join(),让main线程等待t线程结束
我们也可以通过jconsole来验整

join()其他方法
由上面的例子可以知道,当进入一个死循环的线程时,main会得不到结果,那我们可以设置等待时间


如果1000之内,比如刚过500,t就结束了,此时立即继续执行(不会等完3000)
如果超过3000,t还没结束,此时join也继续往下走,就不等了

此时线程没有了main线程
也可以通过增加纳秒的形式来提高精度范围

获取当前线程引用
public static Thread currentThread();
哪个线程调用这个方法,返回哪个线程的引用(类似于this)
休眠当前线程
sleep
sleep(),对于括号里所写的东西,休眠时间通常会比所写的要长一些,当代码遇到sleep时,相当
于让出当前线程,让出cpu资源,后续时间到了,需要操作系统内核,把这个线程重新调到cp
u上,才能继续执行
时间到意味着允许被执行,而不是立即执行
sleep(0)
这是sleep的特殊写法,意味着让当前线程立即放弃cpu资源,等待操作系统重新调度,把cpu让出
来给别人更多执行机会
小总结:
Thread
- 创建线程
- 关键属性
- 终止线程
- 线程等待
- 获取线程引用
- 线程休眠
再谈线程状态
进程状态(操作系统的视角看待)
1.就绪
2.阻塞
线程的状态
(1)NEW: 安排了工作, 还未开始行动
new了Thread对象,还没start
java
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println(t.getState());
t.start();
Thread.sleep(3000);
System.out.println(t.getState());

(2)RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
java
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
System.out.println("t线程结束");
});
t.start();
System.out.println(t.getState());
}

(3)BLOCKED: 这几个都表示排队等着其他事情
也是一种阻塞,比较特殊,由于锁导致的阻塞
(4)WAITING: 这几个都表示排队等着其他事情
死等,没有超时时间的阻塞等待
java
public static void main(String[] args) throws InterruptedException {
final Object oj=new Object();
Thread t=new Thread(()->{
while (true){
}
});
System.out.println(t.getState());
t.start();
t.join();
}

此时main线程一直在等待,无法执行
(5)TIMED_WAITING: 这几个都表示排队等着其他事情
表示排队等着其他事情,有以下三种情况
1.指定时间的堵塞
2.线程堵塞(不参与cpu调度,不继续执行了)
3.阻塞的时间是有上限的
java
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (true){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
System.out.println(t.getState());
t.start();
Thread.sleep(3000);
System.out.println(t.getState());


另外,join(时间)也是会进入到TIMED_WAITING
(6)TERMINATED: 工作完成了.
内核中的线程已经结束了,但是Thread对象还存在
java
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for (int i = 0; i < 2; i++) {
}
});
System.out.println(t.getState());
t.start();
t.join();
System.out.println(t.getState());
}

可以将以上线程状态简化为一张图

调试程序
在多线程程序调试,最重要的是理解线程状态,是调试程序的关键
当发现代码逻辑卡死,可以通过以下几个方法来解决:
1.jconsole/其他工具,查看当前线程中的所有进程,找到对应逻辑的线程是谁
2.看线程状态
看到TIMED_WAITING/WAITING/WAITING,怀疑是不是代码中某个方法产生阻塞,没有被即使唤醒
看到BLOCKED,怀疑是不是代码出现死锁
看到RUNNABLE,线程本身没问题,考虑逻辑上某些条件没有预期触发之类的
3.再看线程具体的调用栈(尤其是阻塞状态,线程代码阻塞在哪一行了)
多线程带来的风险-线程安全
线程不安全的原因
线程是并发执行的,线程调度是随机的
线程不安全的表现
java
private static int count=0;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for (int i = 0; i < 50000; i++){
count++;
}
});
Thread t2=new Thread(()->{
for (int i = 0; i < 50000; i++) {
count++;
}
});
t1.start();
t2.start();
System.out.println(count);
}

看到0,说明main线程先打样了,原因是t1和t2线程执行在很久之后,来不及count++,结果就已经
打印出来
前面我们学习了join(),不妨加上join让main线程等待t1和t2线程

t1和t2谁先join,谁后join无所谓,我们来分情况讨论
(1)t1先结束,t2后结束
main现在t1.join阻塞等待
t1结束
main再在t2.join()阻塞等待
t2结束
main继续执行后续打印 -》 最终结果打印的值就是t1与t2都执行的值
(2)t2先结束,t1后结束
main现在t1.join阻塞
t2结束,t1.join继续阻塞
t1结束,t1.join继续执行
main执行到t2.join,此时t2.join不会继续阻塞
main继续执行打印 -》 最终结果打印的值就是t1与t2都执行的值

结果与预期不相符,当前线程出现了问题,这种情况就叫做线程不安全
这种情况具体是由于线程并发引起的,也和具体代码相关