• Java EE 多线程(二)


    本篇文章思维导图如下:

    目录

    Thread构造方法

    Thread常见属性

    守护线程

    start()方法 -- 创建一个线程

    中断线程 

    总结一下interrupt方法

    等待线程 - join()

    休眠线程sleep

    线程的状态


    Thread构造方法

    Thread() -- thread空构造方法 --创建Thread对象

    Thread(String name) -- 为线程起名,作为与其他线程区分,默认就是Thread-0,Thread-1.......

    Thread(Runnable target) 使用Runnable对象来来创建Thread对象

    Thread(Runnable target,String name),使用Runnable对象创建Thread对象并为线程起名字.

    1. public static class MyRunnable1 implements Runnable{
    2. @Override
    3. public void run() {
    4. System.out.println("我是Thread");
    5. }
    6. }
    7. public static void main(String[] args) {
    8. //为线程起名
    9. Thread t1 = new Thread(()->{
    10. System.out.println("我是Thread!!!");
    11. },"我是Thread线程");
    12. Runnable r = new MyRunnable1();
    13. //使用Runnable对象并为线程起名
    14. Thread t2 = new Thread(r,"我是Thread线程");
    15. }

    Thread常见属性

    • getId()

    获取线程在JVM中的身份标识 ----->作为身份区分

    例如(内核 --pcb标识 , 用户态线程库 标识pthread JVM也有标识)

    • getName()

    得到 --在Thread构造方法传入的名字

    • getPriority

    得到 线程的优先级 ,优先级越高越先被调度

    • getState()

    获取pcb的状态

    • isDaemon()

    判断是否为守护线程

    • isAlive()

    判断线程是否存活

    • isInterrupted()

    判断线程是否中断

    1. public static void main(String[] args) {
    2. Thread t = new Thread (()->{
    3. //System.out.println("我是Thread!!!");
    4. },"我是使用lambda表达式创建的Thread线程");
    5. System.out.println("创建前的状态:"+t.getState());
    6. t.start();
    7. System.out.println("线程名字 : "+t.getName());
    8. System.out.println("线程身份Id : "+t.getId());
    9. System.out.println("当前线程状态 : "+t.getState());
    10. System.out.println("线程是否存活 : "+t.isAlive());
    11. System.out.println("线程是否为守护线程 : "+t.isDaemon());
    12. System.out.println("线程是否中断 : "+t.isInterrupted());
    13. System.out.println("线程的优先级 : "+t.getPriority());
    14. }


    守护线程

    线程分为前台线程和后台线程;

    前台线程:前台线程也是用户线程,默认创建的线程都是前台线程,前台线程会阻止进程结束,进程会保证所有前台线程全部执行完成才会结束整个进程

    后台线程:后台线程也是守护线程十位用户线程而服务的,后台线程不会组织进程结束,进程退出时不会管后台进程结束.

    注意设置后台线程setDaemon(True),判断一个下次线程是否为守护线程利用isDaemon方法,守护线程必须在创建线程之前设置,创建线程之后不能设置


    start()方法 -- 创建一个线程

    创建线程首先需要创建Thread类的实例 --有7种方法创建Thread类实例,要重写run方法,创建实例后我们要调用start()来真正的创建一个线程.

    还要注意start()和run()区别

    • 调用start()方法才是真正的创建一个新的线程,而调用run()只是调用主程序的一个方法并没有创建新的线程
    • 一个线程只能被创建一次,所以start()方法不能被重复调用(如果重复调用会抛出异常),而run方法是一个普通的方法可以重复被调用
    • 调用start()方法不是立即被执行而是将新建状态设置为就绪状态,调用run方法是立即执行任务
       

    中断线程 

    中断线程有两种方式

    • 使用自定义标记法
    1. public static boolean flg = false;
    2. public static void main(String[] args) {
    3. Thread t = new Thread(()->{
    4. while(!flg){
    5. System.out.println("我是Thread!!!");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. t.start();
    14. try {
    15. Thread.sleep(5000);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. System.out.println("我是main");
    20. flg = true;
    21. }

     

    这里设置一个标记,因为线程是并发执行的,等待sleep(5000)然后将flg=true,中断线程(将标记设置为true--结果为false)

    自定义标记法缺点

    1. public static boolean flg = false;
    2. public static void main(String[] args) {
    3. Thread t = new Thread(()->{
    4. while(!flg){
    5. System.out.println("我是Thread!!!");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. System.out.println("终止Thread");
    12. }
    13. });
    14. t.start();
    15. try {
    16. Thread.sleep(1000);
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. flg = true;
    21. System.out.println("我是main");
    22. }

    这时当flag置为true时,依然打印"终止thread"

    所以这里的自定义标记法中断不及时

    因为线程是并发执行的,当把flag标记置为true时,线程还在执行中,无法循环判断,需要等待下一轮判断是才会中断--中断不及时 

    • 使用Thread自带标记法
    1. public static void main(String[] args) throws InterruptedException {
    2. Thread t = new Thread(()->{
    3. //Thread.currentThread().isInterrupted() 默认为false
    4. while(!Thread.currentThread().isInterrupted()){
    5. System.out.println("正在运行中...");
    6. try {
    7. Thread.sleep(1000);
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. });
    13. t.start();
    14. Thread.sleep(3000);
    15. t.interrupt();
    16. System.out.println("线程结束!!!");
    17. }

    为啥抛出异常而线程依然运行呢???

    原因是使用interrupt()不是让线程立即中断,而是发出中断信号触发阻塞的方法抛出异常,具体怎么处理还是交给代码编写者处理.

    ???什么意思

     这里只是让阻塞的方法抛出异常也就是只是打印一下调用栈的信息,然后什么都没有做,继续执行循环.

    我们这里可以在catch里进行设置

    1. break -- 立即退出

    2.使用sleep方法 等待一会在退出

    3.什么也不做,继续执行循环

    1. while(!Thread.currentThread().isInterrupted()){
    2. System.out.println("正在运行中...");
    3. try {
    4. Thread.sleep(1000);
    5. } catch (InterruptedException e) {
    6. //[1]break;//立即退出
    7. //[2]使用sleep等待一会在退出
    8. /*try{
    9. Thread.sleep(1000);
    10. }catch(InterruptedException e1){
    11. e1.printStackTrace();
    12. }
    13. break;
    14. */
    15. //什么也不做继续执行
    16. }
    17. }

    总结一下interrupt方法

    调用interrupt方法相当于发送一个中断指令,而这里不是立即中断.如果没有在阻塞状态会设置标记位然后中断运行,如果处在阻塞状态会让线程内部产生阻塞的方法抛出异常(例如sleep抛出异常),然后具体怎么处理交给代码编写者处理(如立即退出,等待一会退出,还是什么都不做).

    等待线程 - join()

    当我们不想让某个线程与主线程并发执行就可以使用join()  --使用join方法就会让主线程等待(进行阻塞)其他线程执行完成.

    使用join()---等待子线程对象销毁

    当前线程没有执行结束 ,main线程进行阻塞等待,一直到线程run方法执行完(线程对象销毁)

    当线程执行结束,立即返回,main线程执行任务,然后退出进程

    使用join还要抛出异常

    比如要对两个线程使用join()方法时顺序无关(也就是先调用t1还是调用t2无关)

    原因是 : 线程是随机调度的取决于操作系统的调度,

    比如代码 t1.join() t2.join()

    两种情况 当t1先结束时: main线程会阻塞等待,等待t1任务执行完,再去执行线程t2,main线程继续阻塞等待t2执行完.

    当t2先结束时: main线程会阻塞等待,等待t1任务执行完,在执行t2线程,由于t2线程早就执行结束,main线程不用阻塞等待,直接返回,main线程继续执行

    public void join()

    等待线程结束

    public void join(long millis)

    等待线程结束,最多等 millis 毫秒

    public void join(long millis, int nanos)

    同理,但可以更高精度


    直接join是主线程一直等到其他线程结束了,在执行主线程,而这里如果出了bug就容易卡死

    所以优先推荐使用等待一段时间join(毫秒数ms),如果时间到了还没有等到线程任务完成,就要采取一些措施


    一个静态方法来获取当前线程引用

    public static Thread currentThread();

    返回当前线程对象的引用

    对线程进行操作(线程等待,线程中断,获取各种线程的属性),就要获取当前线程引用,通过Thread的静态方法currentThread()方法来获取

    休眠线程sleep

    public static void sleep(long millis) throws InterruptedException

    休眠当前线程 millis
    毫秒

    public static void sleep(long millis, int nanos) throws
    InterruptedException

    可以更高精度的休眠

     有就绪队列和阻塞队列

    就绪队列:pcb在CPU正在运行或立即开始运行,如果阻塞就要从就绪队列到阻塞队列中

    阻塞队列:pcb暂时不在CPU运行,等待休眠结束就从阻塞队列调度回就绪队列


    线程的状态

    线程有以下状态:

    NEW : 新建状态(创建好了Thread实例对象,还没创建线程,安排好工作了,还没有开始工作)

    RUNNABLE : 就绪状态(可能是正在CPU上运行,可能是立即开始运行(还没有在CPU运行,处在就绪))

    WAITING : 阻塞状态 引调用wait方法而等待

    TIME_WAITING : 阻塞状态 : 因调用sleep方法二阻塞等待

    BLOCKED : 阻塞状态 : 等待锁

    TERMINATED : run任务已经执行完毕,线程已经执行结束销毁,但Thread对象还在.

     

    1. public static void main(String[] args) throws InterruptedException {
    2. Thread t = new Thread(()-> {
    3. try {
    4. Thread.sleep(1000);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. });
    9. //线程还未创建处于新建状态
    10. System.out.println(t.getState());
    11. t.start();
    12. //线程正在运行状态处于RUNNABLE状态
    13. System.out.println(t.getState());
    14. Thread.sleep(500);
    15. System.out.println(t.getState());//使用sleep阻塞TIME_WAITING
    16. t.join();
    17. System.out.println(t.getState());//线程t1结束,TERMINATED状态
    18. }

     

  • 相关阅读:
    MySQL 5.7 安装教程(全步骤、保姆级教程)
    Txt病毒
    2023中国智能产业高峰论坛丨文档图像大模型的思考与探索
    【Linux运维系列】vim操作
    【性能优化】优雅地优化慢查询:缓存+SQL修改组合拳
    ElasticSearch在linux上安装部署
    妈妈,我想要学“Jetpack全家桶”,学……学全套……
    JVM——运行时数据区、双亲委派模型、垃圾回收算法、垃圾收集器、Java内存模型
    基于阿奎拉探索方法的灰狼优化算法
    DBeaver manual
  • 原文地址:https://blog.csdn.net/m0_61210742/article/details/126009846