【JavaEE】Java中的多线程 (Thread类)

作者主页:paper jie_博客****

本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。

本文录入于《JavaEE》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将MySQL基础知识一网打尽,希望可以帮到读者们哦。

其他专栏:《MySQL》《C语言》《javaSE》《数据结构》等

内容分享:本期会对JavaEE中一个关于多线程的重要类Thread进行分享~

目录

什么是Thread

创建线程

继承Thread类

实现Runnable接口

匿名内部类创建Thread子类对象

匿名内部类创建Runnable子类对象

lambda表达式创建子类对象

Thread类的方法与常见属性

构造方法

常见属性

常用方法

[启动线程 - start()](#启动线程 - start())

中断线程

引入标记

interrupt()

异常的原因

[等待程序 - join()](#等待程序 - join())

[获取当前线程引用 - currentThread()](#获取当前线程引用 - currentThread())


什么是Thread

Thread类是在Java标准库中的,它可以视为是对操作系统提供的API进一步的抽象与封装. 一个Thread实例对象我们可以认为是一个线程.

创建线程

继承Thread类

这里需要继Thread类来创建一个线程类. 因为这里要重写run方法.里面就是这个线程需要执行的逻辑.

然后还需要创建它的实例,这样一个线程才算创建出来了. 最后需要调用start方法启动线程.

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello Thread");

    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        System.out.println("hello main");
         
    }
}

实现Runnable接口

创建Thread实例,调用Thread构造方法时将Runnable对象作为参数.

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程运行代码");
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        System.out.println("hell main");

    }
}

匿名内部类创建Thread子类对象

这里后面是Thread类的匿名子类.

java 复制代码
public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t = new Thread("这是我"){
            @Override
            public void run() {
                System.out.println("这是匿名方法");
                while(true) {
                    System.out.println("heeh");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();
    }
}

匿名内部类创建Runnable子类对象

java 复制代码
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("匿名创建Runnable子类");
            }
        });
        t.start();
    }
}

lambda表达式创建子类对象

java 复制代码
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("lambda表达式创建子类对象");
        });
        t.start();
    }
}

Thread类的方法与常见属性

构造方法

这里第三个和第四个可以重命名,这样可以更好的进行调试.

常见属性

ID是这个线程的唯一表示,不同的线程ID是不会重复的

名称一般就是调试的时候可以用到

状态表示一个线程当前所处的情况.一般有就绪状态和堵塞状态

优先级表示线程是不是更容易被调度

前台线程的运行会阻止进程的结束,后台线程的运行不会阻止进程的结束.一个进程的结束需要等所有前台线程执行完才会结束,不然就算是main线程执行完了也不会结束.我们创建的线程默认都是前台线程.

**是否存活简单来说就是run方法是否执行完了.**Java中Thread实例虽然代表的是多线程,但是它的生命周期和内核中创建出来的线程PCB的生命周期不是一样的.

此时虽然Thread实例对象有了,但是内核中线程PCB还没创建,isAlive是false.只有在t.start()执行后内核中的PCB才会创建出来,这时isAlive为true

当run方法执行完后,内核中的PCB就销毁了,这时isAlive为false.

常用方法

启动线程 - start()

**这里Thread实例对象虽然创建出来了,但是线程并未真正的启动.调用start()后才算真正的创建出了一个线程.**这里的start()才是在内核中创建出一个线程. 调用start()创建线程本质上就是调用系统的API来创建线程.

这里一个线程只能调用一次start(),再次使用start需要用另一个线程对象来调用.不然它会报出一个非法线程状态的异常.

java 复制代码
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("线程");
            }
        };
        thread.start();
        thread.start();
        System.out.println("hell main");
    }
}

中断线程

中断一个线程就是提前让这个线程的run方法结束.常用的方法有两种:

  1. 通过引入一个标记

  2. 通过interrupt()方法

引入标记

这里通过flag这个标记来让run方法提前结束.当main线程执行到flag = true时,另一线程就会提前结束.

java 复制代码
public class ThreadDemo2 {
    public static  boolean flag = false;
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!flag) {
                    System.out.println("hell Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("终止这个线程");
        flag = true;
    }
}

这里注意,flag标识是不能在main方法内中,不能作为局部变量.虽然lambda匿名内部类可以通过变量捕捉访问到外面的局部变量.但是这个局部变量必须是不可变的,是final修饰的,这就和我们需要通过改变标记来终止线程发生冲突了,这就不可行.

必须是final是因为main方法和Thread都有自己的函数栈帧.他们生命周期不同.flag是在属于main的栈帧中,一但main执行完了,它的栈帧就会销毁,Thread再想使用就用不到了. 这里变量捕捉就是为了这个而诞生.它就是传参,本质上就是在需要的线程上将flag拷贝一份.为了保证flag的一致性就让它不能改变.

interrupt()

Thread里面自带一个标志位.我们可以通过方法来获取和改变这个标志位.

这里可以用Thread.interrupted()或者Thread.currentThread().isInterrupted()来获取内置的标志位.

interrupt()方法可以改变标志位.

这里使用了Thread.currentThread().isterrupted:

java 复制代码
public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("hell Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("中断它");
        thread.interrupt();

    }
}

但运行代码后我们会发现问题:

这里会发现它会抛出一个中断异常,然后继续运行,并没有停下来.

异常的原因

这时因为这里interrupt()方法提前唤醒了sleep,这个时候sleep就会做两件事:

1. 抛出InterruptedExecption这个异常

2. 将内置标志位还原.

所有这会导致线程继续运行.

需要线程停下来,处理方法我们在catch里面加上break就可以了.

这里在catch中我们有三种处理方法:

1. 让线程立刻停下来

2. 让线程先运行一些代码再停下来

3. 让线程不停下来继续运行

我们处理异常也有几种常见的方法:

等待程序 - join()

join方法可以调整线程执行的先后顺序.虽然说线程的调度执行是随机调度的.但这里join可以将线程进行堵塞从而影响到了线程的执行先后顺序.join所在的线程会发生堵塞,在调用join方法线程运行完后,这个线程的堵塞状态才会解除.

注意: 这里是调用join()的线程被等待先执行,join()所在的线程等待,后执行.

join()方法有三种:

第一种: 死等,需要等等待的线程执行完才会解除堵塞状态

第二种: 有时间的等待, 在一定时间内进行堵塞.超出时间范围就会解除堵塞状态

第三种:精确到微秒的有时间等待.

一般情况下,我们最常用的就是第二种情况:

java 复制代码
public class ThreadDemo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(true) {
                System.out.println("hell main");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
        thread.join();
        while(true) {
            System.out.println("hell ");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

这段代码,就是main线程进入堵塞状态,等待Thread线程结束. 虽然说Thread这个线程这执行中可以和其他多个线程共同进行调度执行,但由于main线程一直在等待,就算Thread线程在CPU上进行了多次切换也不影响这个线程先执行完.

这里注意: 我们的interrupt方法可以将join线程提前唤醒.

获取当前线程引用 - currentThread()

在使用类继承Thread创建线程方法我们可以用this直接引用这个对象.但是当使用lambda/匿名内部类/Runnable时this就不再指向Thread对象了.这时我们获取Thread对象引用就需要使用currentThread()方法了.

java 复制代码
public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello Thread");
            System.out.println(Thread.currentThread().getName());
        });
        t.start();
    }
}

休眠当前线程 - sleep()

sleep可以将当前线程休眠一定时间,这个时间可以自己设定.但使用它需要抛出异常或者try- catch.这里休眠的时间因为线程调度的不可控,一般都会大于等于设定的时间.

它也有两种方法:

一般都是使用第一种,第二种是精确到微秒.

java 复制代码
public class ThreadDemo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1111);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("休眠1.111秒");
        });
        t.start();
    }
}

相关推荐
暗黑起源喵5 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong9 分钟前
Java反射
java·开发语言·反射
九圣残炎43 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge1 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐1 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航2 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself2 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言