【JavaEE初阶】多线程(2)

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

线程的核心操作

创建线程start

线程的状态

线程的终止

定义一个变量实现

利用标志位实现

[加上break/return 结束线程](#加上break/return 结束线程)

线程等待

[join 无参数版本](#join 无参数版本)

两个线程等待

多个线程等待

缺陷

join有参数版本


线程的核心操作

创建线程start

一个经典的面试题:start和run之间的区别

  • start:调用系统函数,真正在系统内核中创建线程( 创建PCB,加入到链表中),此处的start 会根据不同的系统,分别调用不同的api(windows,linux,mac...),创建好新的线程,再单独来执行run.
  • run:描述了线程要执行的任务,也可以称为"线程的入口"

start的执行速度一般是比较快的(创建线程,比较轻量),一旦start执行完毕,新线程就会开始执行,调用start的线程,也会执行main. 调用start,不一定非得是main线程调用,任何线程都可以创建其他线程,如果系统资源充裕,就可以任意的创建线程(但线程不是越多越好)

start的本质调用系统的api,系统的api会在系统内核里 ,创建线程(创建PCB,加入链表)

线程的状态

由于Java希望一个Thread对象 只能对应到一个系统中的线程,因此会在start中 根据线程的状态做出判定:

  • 如果Thread对象 没有调用过start,此时状态是一个NEW状态,接下来就可以顺利调用start
  • 如果已经调用了start,就会进入到其他状态,只要不是NEW状态,接下来start就会抛出异常

所以再次强调: 一个Thread对象,只能调用一次start,如果多次调用就会出问题(一个Thread对象,只能对应系统中的一个线程)

线程的终止

Java中让一个线程结束是一个更**'温柔** '的过程,比如B正在运行着,A想让B结束,其实核心是A想办法(如何将B的run能够更快 的执行完)让B的run方法执行完毕,此时B自然结束了, 而不是B的run执行一半,A直接把B强制结束.

定义一个变量实现

复制代码
public class Demo6 {
    private static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while(! isQuit){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t线程结束.");
        });

        t.start();
        Thread.sleep(1000);

        System.out.println("main线程尝试终止 t线程");
//修改isQuit变量,就能够影响到t线程结束
        isQuit=true;
    }
}

其中判断条件isQuit要写在main方法的外面 作为成员变量,内部类(lambda表达式本质上是一个'函数接口'产生的 匿名内部类)是可以访问外部类的成员

知识复习: 变量捕获

变量捕获是lambda表达式/匿名内部类 的一个语法规则

isQuit 和 lambda定义在一个作用域中,此时的lambda内部是可以访问到lambda外部 的,Java把同作用域中的所有变量都给捕获了,但是Java的变量捕获要求捕获的变量得是final/ 事实final(这是Java的特殊限制,c++,js等语言没有这个限制)

利用标志位实现

Interrupt方法能够设置标志位, 也能唤醒sleep等阻塞方法.

sleep被唤醒之后,又能清空标志位

复制代码
public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t =new Thread(()->{
            Thread currentThread =Thread.currentThread();
            while(!currentThread.isInterrupted()){
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();
        Thread.sleep(3000);
        //在主线程中 控制t线程被终止,设置上述标志位
        t.interrupt();
    }
}

Thread类里面有一个成员变量 是interrupted (Boolean类型),初始情况下这个变量是false(未被终止),但是一旦外面的其他线程调用一个interrupt方法,就会设置上述标志位

修改代码,停止报错

加上break/return 结束线程

所以,Java中 终止线程是一个'温柔' 的过程,不是强行终止 ,举例: 如果A希望B线程终止,B收到这样的请求后,B需要自行决定 是否要终止/立即 /稍后 终止(B线程内部的代码自行决定,其他线程无权干涉)

  • 如果B线程想**无视A,**就直接catch中,啥都不做,B线程仍然会继续执行(sleep清除标志位,就可以使B能够做出这样的选择,否则B势必会结束)
  • 如果B线程想立即结束,就直接在catch中加上return或者break.此时B线程会立即结束
  • 如果B线程想稍后结束,就可以在catch中加上一些其他的逻辑(比如 释放资源,清理一些数据,提交一些结果...收尾工作) 这些逻辑之后,再进行return或者break.

线程等待

操作系统 针对多个线程的执行,是一个 '随机调度,抢占式执行'过程, 线程等待 就是在确定两个线程的'结束顺序'(无法确定两个线程调度执行的顺序,但可以控制 谁先结束,谁后结束), 本质上是让后结束的线程 等待先结束的线程 即可(此时后结束的线程就会进入阻塞 ,一直到先结束的线程真的结束了,阻塞才解除)

join 无参数版本

对于join来说,无参数版本会持续等('死等'). 被等待的线程,只要不执行完,这里的等待就会持续阻塞

两个线程等待

使用join 比如,有两个线程a和b**,在a中调用b.join** ,表示让a线程等待b线程先结束,然后a再继续执行(b是被等待的一方,先结束)

复制代码
public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("这是线程t");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t线程结束");
        });
        t.start();
        
        //让主线程 等待t线程
        System.out.println("main线程开始等待");
        t.join();
        System.out.println("main线程等待结束");
    }
}

如果上面的 t线程先结束了,main线程就不必等待了(join就不会发生阻塞),join 就是确保被等待的线程能够结束,如果已经结束了,join就不必在等了.

任何的线程之间都是可以相互等待的

下面是 t线程等待 主线程的代码示例

复制代码
public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        //t线程等待主线程
        Thread mainThread =Thread.currentThread();//拿到主线程对象

        Thread t =new Thread(()->{
            System.out.println("t线程开始等待...");
            try {
                mainThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t线程等待结束");
        });
        t.start();
        Thread.sleep(2000);
        System.out.println("main线程执行结束.");

    }
}

获取主线程对象 和 sleep的理解:

多个线程等待

相互等待的线程 也不一定是两个线程之间,一个线程也可以同时等待多个别的线程,或者若干个线程之间也能相互等待

复制代码
public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("线程t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1执行结束");
        }) ;

        Thread t2 =new Thread(()->{
            for (int i = 0; i < 4; i++) {
                System.out.println("线程t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2执行结束");
        });
        t1.start();
        t2.start();
        System.out.println("main线程开始等待...");
        t1.join();
        t2.join();
        System.out.println("main线程结束等待.");
    }
}

上述代码中,主线程等待t1和t2线程,t2线程等待t1,所以结束顺序是t1, t2, main线程

t1, t2和主线程这三个日志的顺序是不确定的(不确定哪个先执行),但是由于t1和t2有创建开销,一般来说是主线程先执行,t1和t2 的顺序不确定.

缺陷

对上述 join无参数版本会出现'死等'的状态 一旦被等待线程代码 出现了一些bug ,就可能使这个线程迟迟无法结束,从而使等待的线程一直无法执行其他操作.

join有参数版本

相关推荐
24k小善44 分钟前
Flink TaskManager详解
java·大数据·flink·云计算
想不明白的过度思考者1 小时前
Java从入门到“放弃”(精通)之旅——JavaSE终篇(异常)
java·开发语言
.生产的驴1 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
猿周LV1 小时前
JMeter 安装及使用 [软件测试工具]
java·测试工具·jmeter·单元测试·压力测试
晨集1 小时前
Uni-App 多端电子合同开源项目介绍
java·spring boot·uni-app·电子合同
时间之城1 小时前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
椰羊~王小美2 小时前
LeetCode -- Flora -- edit 2025-04-25
java·开发语言
凯酱2 小时前
MyBatis-Plus分页插件的使用
java·tomcat·mybatis
程序员总部2 小时前
如何在IDEA中高效使用Test注解进行单元测试?
java·单元测试·intellij-idea
oioihoii2 小时前
C++23中if consteval / if not consteval (P1938R3) 详解
java·数据库·c++23