JavaEE|多线程(三)

等待一个线程 - 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. 关键属性
  3. 终止线程
  4. 线程等待
  5. 获取线程引用
  6. 线程休眠

再谈线程状态

进程状态(操作系统的视角看待)

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都执行的值

结果与预期不相符,当前线程出现了问题,这种情况就叫做线程不安全

这种情况具体是由于线程并发引起的,也和具体代码相关

相关推荐
卷到起飞的数分3 小时前
JVM探究
java·服务器·jvm
Geek攻城猫3 小时前
Java生产环境问题排查实战指南
java·jvm
OtIo TALL10 小时前
redis7 for windows的安装教程
java
uNke DEPH11 小时前
Spring Boot的项目结构
java·spring boot·后端
xixingzhe211 小时前
idea启动vue项目
java·vue.js·intellij-idea
超级无敌暴龙兽11 小时前
和我一起刷面试题呀
前端·面试
wzl2026121311 小时前
企业微信定时群发技术实现与实操指南(原生接口+工具落地)
java·运维·前端·企业微信
小码哥_常11 小时前
Robots.txt:互联网爬虫世界的“隐形规则”
前端
凌波粒11 小时前
Java 8 “新”特性详解:Lambda、函数式接口、Stream、Optional 与方法引用
java·开发语言·idea