【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有参数版本

相关推荐
duration~33 分钟前
Maven随笔
java·maven
zmgst36 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong2 小时前
Java反射
java·开发语言·反射
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚3 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐3 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven