javaThread类 API详解

本文介绍了Java中线程的各种状态,包括NEW、RUNNABLE、BLOCKED等,并详细解析了线程间的切换机制及其带来的开销。此外,还深入探讨了Thread类的主要API方法,如start()、run()、sleep()等的功能与使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 线程状态转换图

Java中线程从创建到最终消失,要经历以下几个状态,

    NEW   ,线程还没启动
    RUNNABLE 线程运行在jvm中,但是在等待cpu 
    BLOCKED  线程在等待monitor lock
    WAITING, 	一个线程等待另一个线程的特定操作结果,一般是由于调用了如下方法wait(),join(),park()方法,没有time
    
     一个线程在这个状态由于等待时间,一般是由于线程调用了下面方法
     Thread.sleep()
     Object.wait with timeout
     Thread.join  with timeout
      LockSupport.parkNanos
      LockSupport.parkUntil
    TIMED_WAITING,
	线程执行结束
    TERMINATED;
}

在这里插入图片描述

  • 阻塞和等待的区别

阻塞: 当一个线程试图获取对象锁(非java.util.concurrent库的锁,即synchronized),而该锁被其他线程持有,则这个线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程显示唤醒自己,不响应中断。

等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显示的唤醒自己,实现灵活,语义丰富,可响应中断。例如调用:Object.wait(),Thread.join(),以及等待Lock,或Condition.

这个地方sychronized和JUC里面的lock都实现锁的功能,但线程进入状态是不一样的。synchronzied会让线程进入阻塞态,而JUC里的lock是用LockSupport.park()/unpart()来实现阻塞/唤醒的,会让线程进入等待状态,这里虽然等待锁进入的状态不一样,但是被唤醒后又进入runnable态,从行为效果来看又是一样的。

二 线程切换

   对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程切换(对于进程也是类似)。
  由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。 因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。

虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

三 Thread 类API


  • start() 这个方法使线程开始执行,由jvm调用这个线程的run方法,这个方法使得产生两个线程,一个当前线程(运行start的),一个运行run的线程。
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }


  • run()
    run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当该线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

  • sleep()
    sleep使得线程休眠,交出cpu的执行权,sleep方法不会释放锁,也就是说如果当前线程持有锁,即使调用sleep方法,其他线程也无法访问这个对象。

  • setDaemon​(boolean on),将此线程标记为 守护线程。 当运行的线程都是守护进程线程时,Java虚拟机将退出。 在启动线程前调用这个方法。
    引用场景:为了维护连接发送心跳包的线程。java种的gc线程也是守护线程,

  • yield()
    调用yield方法会让当前线程交出cpu权限,让cpu去执行其他线程,但是可能cpu还是会执行这个线程,yield方法只是让其他线程有获取cpu执行时间的机会。和sleep方法一样,不会释放锁。
    这里yield方法并不会让线程进入阻塞状态,而是让线程重回runnalble状态,它只需要重新获取cpu执行时间,这一点和sleep方法不一样的。

  • join()
    jion()实际上是利用 了wait()。只不过它不用等到notify/notifyAll,它的结束条件是
    (1) 等待时间到(2)目标线程已经运行完(通过isAlive()来判断)
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
		
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
		//时间为0,就要一直等待目标线程跑完
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {  //如果目标线程没运行完,就按照时间等待
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }


  • interrupt()
    此操作会将线程的中断标志位置位,至于线程作何动作那要看线程了

    • 如果线程sleep(),wait(),join()等处于阻塞状态, 那么线程会定时检查中断状态位如果发现中断状态位为true,那么这些阻塞方法会抛出InterruptedException异常,并且在抛出异常后会立即将线程中断状态位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。
    • 如果线程正在运行,争用synchronized,lock()等,那么是不可中断的,他们会忽略

可以通过以下三种方式来判断中断:

1)isInterrupted()

此方法只会读取线程的中断标志位,并不会重置。

2)interrupted()

此方法读取线程的中断标志位,并会重置。

3)throw InterruptException

抛出该异常的同时,会重置中断标志位。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值