JavaEE|多线程(二)

线程的命名

可以给线程起名(中文名也是可以的),例如对三个线程起不同名字再去jconsole上观察

java 复制代码
public class Demo06 {
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while(true){
                System.out.println("hello t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2=new Thread(()->{
            while(true){
                System.out.println("hello t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t3=new Thread(()->{
            while(true){
                System.out.println("hello t3");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    

当不给线程起名的时候,线程会自动命名为Thread-0,Thread-1....

通过名字描述线程是干啥的,方便调试

主线程

main方法执行完毕,主线程就已经结束了

以前我们对于main的认识是当main方法执行完之后,程序就已经结束了(进程);但实际上,以前的

认知是针对于单线程程序

Thread 类及常见方法

Thread 类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread 对象与之关 联。

Thread 的常见构造方法

|-----------------------------------------------|----------------------------|
| 方法 | 声明 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 创建线程对象,并且命名 |
| Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并命名 |
| 【了解】Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,了解即可 |

Thread 的几个常见属性

|--------|-----------------|
| 属性 | 获取方法 |
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrupted() |

ID 是线程的D唯一标识,不同线程不会重复,标识线程身份的效果,类似于PID

Daemon

关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

例如,当main线程虽然结束了,但是t1,t2.t3还在,所以进程仍然存在,t1,t2,t3这样的线程就称为

"前台线程"

黑框部分是JVM自带的线程,这些线程不影响进程的结束,如果进程要结束了,即使这些线程继续

运行,也会随之结束,这些就是后台线程

我们创建的代码包括main线程都默认是前台线程,但可以通过setDaemon方法来进行修改

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
           while (true){
               System.out.println("hello thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.setDaemon(true);
        t.start();
        for (int i = 0; i < 2; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
        System.out.println("main结束");
    }

把t设为后台线程,无力阻止main线程的结束,这样的设置得在start之前运行

isAlive()

创建Thread对象和系统中的线程关系是一一对应的,但是Thread对象的生命周期和系统中的线程的生命周期是不同的(可能存在,Thread对象存活,但是系统中的线程已经销毁的情况)

java 复制代码
  public static void main(String[] args) throws InterruptedException {
        Thread t=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(t.isAlive());
        t.start();
        while (true){
            System.out.println(t.isAlive());
            Thread.sleep(1000);
        }

当for循环结束之后,线程的入口方法的逻辑结束了,系统中对应的线程就随之销毁了

但是对应的t对象会仍然存在

启动线程-start()

java标注库/JVM提供的方法,本质上是调用操作系统的API

每个Thread对象,都是只能start一次的

每次创建一个新的线程,都得创建一个新的Thread对象(不能重复利用)

start和run的区别

run是线程的入口方法,不需要手动调用

start是调用系统api

二者本质上没有很大的关系

中断线程

1.通过共享的标记来终止

java 复制代码
  private static  boolean isFinished=false;
    public static void main(String[] args) throws InterruptedException {

        Thread t=new Thread(()->{
            while (!isFinished){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        isFinished=true;
    }

通过定应全程变量来控制线程进度

但当isFinished变为局部变量时,此时无法对其进行修改

通过观察报错原因我们可以知道,这个isFinished应该为常量

这与我们前面的isFinished修改相互矛盾,这其实是因为lambda里面希望使用外面的函数,触发了

变量捕获的语法

lambda是回调函数,执行时机是很久之后(操作系统真正创建线程之后,才会执行),可能会出

现后续线程创建好了,但是当前main这里的方法都执行完了,对应的isFinished就销毁了

Java为解决上述的问题把捕获的变量拷贝一份在lambda里面,外面的变量是否销毁都不会影响

lambda里面的执行

这里的拷贝意味着这样的变量就不再适合修改(修改的本质是修改一方,另一方不会随之变化)

让isFinished变为静态变量时,isFinished在类加载时就会存在,而不是通过new时才会存在

而对于引用类型,引用本身是不能修改(不能修改这个引用指向的其他对象),但是引用指向的对象

本体是可以修改的

把上述的代码改成员变量此时不再是"变量捕获"语法,而是切换成"内部类访问外部类的成员"语法

lambda本质上是函数式接口,相当于一个内部类,isFinished就是外部类的成员,内部类就是能够

访问到外部类的成员,成员变量生命周期也是让GC来管理的,在lambda里面不担心变量生命周期

失效的问题,也就不必拷贝,也就不必限制final之类的

调用 interrupt() 方法

Java的Thread对象中提供了现成的变量,直接进行判定,不需要自己创建了

lambda这里的定义是在new Thread之前,也是在Thread t声明之前,所以Thread创建的对象也无

法在lambda表达式中使用

Thread提供了访问当前对象的静态方法currentThread(),获取到的就是那个线程的Thread引用

java 复制代码
public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t结束了");
        });
        t.start();
        Thread.sleep(3000);
        System.out.println("main尝试终止t线程");
        t.interrupt();
        System.out.println(Thread.currentThread().getName());
    }

判定Thread里的boolean变量的值,判定线程是否终止了

主动去进行终止,修改这个boolean变量的值,除了设置boolean变量(标志位)之外,还能够唤醒

sleep这样的阻塞方法

这个线程掀桌了,可以通过修改抛出异常的部分来控制终止

加上break是立即终止

啥都不写就是不终止

catch中执行一些逻辑在break,就是稍后终止

相关推荐
赏金术士12 小时前
Kotlin 从入门到进阶 之作用域函数 & 优雅写法(五)
android·开发语言·kotlin
openKaka_12 小时前
从 scheduleUpdateOnFiber 到 Root 微任务调度:React 如何把更新交给调度系统
开发语言·前端·javascript
人道领域12 小时前
【黑马点评日记】:用户签到功能详解——从Bitmap入门到避坑指南
java·数据库·redis·后端
梦梦代码精12 小时前
《企业开源商城选型:商业闭环、二次开发与成本平衡》
java·开发语言·低代码·开源·github
前进的李工12 小时前
智能Agent实战指南:记忆组件嵌入技巧(记忆)
开发语言·前端·javascript·python·langchain·agent
狼与自由12 小时前
灰度发布的策略
java
神仙别闹12 小时前
基于QT(C++)实现线性表的建立、插入、删除、查找等基本操作
java·c++·qt
测试员周周12 小时前
【AI测试功能5】AI功能测试的“黄金数据集“构建指南:从0到1搭建质量评估体系
运维·服务器·开发语言·人工智能·python·功能测试·集成测试
蓝眸少年CY12 小时前
Scala - 基础教程
开发语言·后端·scala
Kiyra12 小时前
从上传到可问答:Interview Agent 的知识库 RAG 链路
java·人工智能·后端·spring·职场和发展