3、多线程的操作和守护线程

本文详细讲解了多线程中的状态切换机制,包括sleep(), join(), wait(), notify()的用法,以及stop()和interrupt()方法的区别。同时介绍了守护线程的概念,如何设置和理解线程的守护角色。
摘要由CSDN通过智能技术生成

3、多线程的操作和守护线程

一、多线程的操作

书接上回,讲了多线程的一些基本概念,本节侧重

  • 多线程状态之间是如何切换
  • 切换操作的原理

请添加图片描述

  • 上节有关多线程的操作涉及到了sleep()、join()、wait()、notify();

1、sleep()

sleep方法 会主动释放CPU的执行权,休眠一段时间。但不会释放锁(就会一直占用着资源不释放)

线程状态:运行状态→限时等待状态 (等到休眠时间结束:限时等待状态→就绪状态→运行状态)

  • sleep方法定义在Thread类中,是一组静态方法,有两个重载版本:
//使目前正在执行的线程休眠 millis 毫秒;
public static void sleep(long millis) throws InterruptException//使目前正在执行的线程休眠 millis 毫秒,nanos 纳秒
public static void sleep(long millis,int nanos) throws InterruptException

​ sleep 方法会有 InterruptException 受检异常抛出,如果调用了 sleep 方法,必须进行异常审查,

捕获 InterruptedException 异常,或者再次通过方法声明存在 InterruptedException 异常。

记住这个InterruptedException

2、手动线程退出的方式

之前几篇文章也讲了一些关于线程结束的方式:

  • 线程执行完异步逻辑任务,即run()方法里的代码
  • 线程执行异步任务的过程出现运行时异常并没有捕获,JVM会中断线程的执行
    • synchronized会自动释放锁,lock不会,所以lock要在finally代码块里unlock才能释放锁

接下来我们了解一下关于手动结束线程的方式:

  1. stop()方法
  2. 优雅地手动结束线程,interrupt方法
2.1 stop()方法

stop()方法会强制结束线程,但stop()方法的使用是危险的。Object.stop()不推荐使用

原因分析:假如线程持有某把锁(持有共享资源的使用权),通过stop()结束该线程,会导致线程持有的锁不能释放,会产生死锁的问题。

2.2 Interrupt()中断线程

Thread的interrupt()方法并不能中断线程,该方法的本质不是用来中断线程,而是将线程设置为中断状态

究其原因,我们得讲一下interrupt()的两个作用:

  1. 如果此线程处于阻塞状态(如调用了Object.wait()方法),则会立马退出阻塞,并抛出InterruptException异常。线程就可以通过捕获 InterruptedException 来做一定的处理,然后让线程退出。

更确切的说,如果线程被Object.wait(),Thread.join和Thread.sleep三种方式之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个InterruptExcption中断异常(该线程必须实现预备好处理此异常),从而提早地终结被阻塞状态

2.如果该线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为true。所以,程序可以在适当的位置,通过调用isInerrupted()方法查看自己是否被中断,并作退出操作

public class InterruptUsing {
    static class SleepThread extends Thread{
        @Override
        public void run() {
            //线程休眠10s

            try {
                System.out.println("进入休眠");
                Thread.sleep(10000);
                //.....执行异步逻辑任务
            } catch (InterruptedException e) {
                System.out.println("发生异常被中断");
                //这里我们可以手动结束线程
                return;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        SleepThread sleepThread=new SleepThread();
        sleepThread.start();
        System.out.println("等待2s...");
        //主线程执行2s后中断 sleepThread线程
        Thread.sleep(2000);
        sleepThread.interrupt();
    }
}

请添加图片描述

Thread.interrupt( )方法并不像 Thread.stop( )方法那样中止一个正在运行的线程,其作用是设置线程的中断状态位(为 true),至于线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。

关于为什么线程能够判断自己被interrupt从而抛出异常,是由于线程内部的isInterrupt()会时不时执行判断线程的中断标识位是否位true

3、线程的join操作

线程合并的定义和原理就不谈了,不懂可以看完上一篇文章。

线程的 join 操作的三个版本

Join()方法是 Thread 类的一个实例方法,有三个重载版本:

//重载版本 1:此方法会把当前线程变为 WAITING,直到被合并线程执行结束。
public final void join() throws InterruptedException//重载版本 2:此方法会把当前线程变为 TIMED_WAITING,直到被合并线程结束,或者等待被合并
线程执行 millis 的时间。
public final synchronized void join(long millis) throws InterruptedException//重载版本 2:此方法会把当前线程变为 TIMED_WAITING,直到被合并线程结束,或者等待被合并
线程执行 millis+nanos 的时间。
public final synchroinzed void join(long millis, int nanos) throws InterruptedException

4、线程yield操作

线程的 yield(让步)操作的作用:是让目前正在执行的线程放弃当前的执行,让出 CPU 的执行权限,使得 CPU 去执行其他的线程。

处于让步状态的 JVM 层面的线程状态,仍然是RUNNABLE 可执行状态;但是,该线程所对应的操作系统层面的线程,在状态上来说,会从执行状态变成就绪状态。

yield( )方法是 Thread 类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是让线程转入就绪状态。

5、关于线程阻塞Blocked

处于阻塞状态的线程不会占用CPU资源(释放了锁),什么情况下会进入阻塞状态

  1. 线程等待获取锁

    ​ 等待获取一个锁,而该锁被其他线程持有,则线程阻塞状态。当其他线程释放了该锁,并线程调度器允许该线程持有该锁时,该线程退出阻塞状态

  2. IO阻塞

    线程发起一个阻塞式IO操作后,如果不具备IO操作的条件,线程会进入阻塞状态。IO包括磁盘IO和网络IO

6、wait方法和notify方法原理

上一篇文章主要讲了线程通信的使用,这篇文章不讲

6.1对象的 wait 方法的主要作用是让当前线程阻塞并等待被唤醒。

wait 方法与对象监视器(Monitor)紧密相关,使用 wait 方法时也一定需要放在同步块中。

Object 类中的 wait 方法,有三个版本:

1void wait( ) 
//这是一个基础版本,当前线程调用了同步对象 locko 的 wait 实例方法后,将导致当前的线程等待,当前线程进入 locko 的监视器 WaitSet,等待被其他线程唤醒。2void wait(long timeout ) 
//这是一个限时等待版本,导致当前的线程等待,等待被其他线程唤醒,或者指定的时间 timeout 用完,线程不在等待。3void wait(long timeout, int nanos) 
//这是一个高精度限时等待版本,其主要作用是更精确控制等待时间。参数 nanos 是一个附加的纳秒级别等待时间,从而实现更加高精度的等待时间控制。
    //1 秒 =1000 毫秒 = 1000 000 微秒 = 1000 000 000 纳秒。
6.2 wait方法的原理
  1. 当线程调用了locko(某个同步锁对象)的wait方法后,JVM会将当前线程加入lock监视器的WaitSet(等待集),等待被其他线程唤醒
  2. 当前线程会释放locko对象监视器的Ownder权力,让其他线程可以抢夺locko对象的监视器
  3. 让当前线程等待,进入WAITING状态

请添加图片描述

6.3对象的notify方法

对象的 notify 方法的主要作用是唤醒在等待的线程。notify 方法与对象监视器紧密相关,使用 notify 方法时也需要放在同步块中。

notify 方法有两个版本:

版本一:void notify( ) 
//notify 方法的主要作用如下:locko.notify 调用后,唤醒 locko 监视器等待集中的第一条等待线程;被唤醒的线程进入 EntryList,其状态从 WAITING 等待状态变成 BLOCKED 阻塞状态;
版本二:void notifyAll( ) 
//locko.notifyAll 被调用后,唤醒 locko 监视器等待集中的全部等待线程;所有被唤醒的线程进入 EntryList,线程状态从 WAITING 等待状态变成 BLOCKED 阻塞状态。
6.4 notify方法的原理

对象的 notify(或者 notifyAll)方法的核心原理,大致如下:

  1. 当线程调用了locko(某个同步锁对象)的notify或notifyAll方法后,JVM会唤醒locko监视器WaitSet中的一条或所以的的等待线程
  2. 等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器的资格,其状态从WAITING转变为Blocked
  3. EntryList的线程抢夺到监视器Owner权力后,线程的状态变为Runnable,具备重新执行的资格

请添加图片描述

6.5线程通信“通知-等待”要点
  1. 使用某个同步对象 locko 的 wait 和 notify 类型方法前,必须要取得这个锁对象的监视锁,所以,wait 和 notify 类型方法必须放在 synchronized(locko)同步块中,如果没有获得监视锁,则 JVM 会报 IllegalMonitorStateException 运行时异常。
  2. 使用 wait 方法时使用 while 进行条件判断:如果是在某种条件下进行等待,对条件的判断不能使用 if 语句做一次性判断,而是使用 while 循环做反复判断。只有这样,才能在线程被唤醒后继续都检查 wait 的条件,并在条件没有满足的情况下,继续等待。

二、守护线程

线程分为守护线程和用户现场(default)。

1、守护线程和用户线程的区别

两者的本质区别:JVM虚拟机进程终止的方向不同。用户线程和JVM线程是主动关系,即如果全部的用户线程终止,JVM虚拟机进程也随即终止;而守护线程和JVM进程是被动关系,即如果JVM进程终止,所以守护线程也随之终止

请添加图片描述

只要有一个用户线程没有终止,JVM进程也不会终止。

2、守护线程的要点
  1. 守护线程必须在启动前,将其状态设置为true;线程启动后,不能再将用户线程设置为守护线程。否则,JVM 会抛出一个 InterruptedException 异常。
  2. 守护线程有被JVM强制终止的风险,所以,在守护线程中尽量不去访问系统资源,如数据库等,守护线程被强制终止,可能会引发系统资源操作的不负责中断,从而导致资源不可逆的损坏
  3. 守护线程创建的线程也为守护线程

怎么理解呢,就是在守护线程内创建的新的线程都是守护线程。但在创建后,如果调用setDaemon(false)将新的线程显示的设置为用户线程,新的线程可以调整成为用户线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值