Java实现并发的主要手段是多线程。线程是操作系统中的一个概念。Java语言中的线程本质上就是操作系统线程,它们是一一对应的。如果要了解操作系统中线程的生命周期,就需要了解生命周期的各种状态是如何过渡的。接下来,让我们先了解操作系统的线程生命周期,再学习Java中线程的生命周期。操作系统中线程的生命周期操作系统的线程生命周期基本上可以用下图所示的五态模型来描述。这五种状态分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。五态模型的细节如下所示。1.初始状态:表示线程已经创建,但是不允许分配CPU执行。这种状态是编程语言特有的,但这里所谓的创建只是在编程语言层面创建,而在操作系统层面,真正的线程还没有创建。2.可运行状态:指线程可以分配CPU执行。在这个状态下,真正的操作系统线程已经成功创建,所以可以分配CPU执行。3、当有空闲的CPU时,操作系统会将其分配给一个处于可运行状态的线程,分配给CPU的线程的状态就会转换为运行状态。4.如果处于运行状态的线程调用了阻塞的API(比如以阻塞的方式读取文件)或者等待一个事件(比如条件变量),那么线程的状态就会切换到休眠状态,并同时释放CPU使用权,休眠状态的线程永远没有机会获得CPU使用权。当等待事件发生时,线程将从睡眠状态转换到可运行状态。5、线程执行完毕或发生异常后,会进入终止状态。处于终止状态的线程不会切换到任何其他状态。进入终止状态意味着线程的生命周期结束了。在Java中,runnable状态和running状态是合并的。这两种状态在操作系统调度层面是有用的,但是JVM层面并不关心这两种状态,因为JVM把线程调度交给了操作系统。Java还改进了睡眠状态等。Java中线程的生命周期接下来我们看一下Java中线程的生命周期。Java中线程有六种状态,分别是:1.NEW(初始化状态)2.RUNNABLE(可运行/运行状态)3.BLOCKED(阻塞状态)4.WAITING(无时间限制等待)5.TIMED_WAITING(有时间等待limit)6.TERMINATED(终止状态)在操作系统层面,BLOCKED(阻塞状态)、WAITING(无时间限制等待)、TIMED_WAITING(限时等待)是一种状态,休眠状态。也就是说,只要Java线程处于这三种状态之一,该线程就永远没有CPU的使用权。因此,Java线程的生命周期可以简化为下图:其中BLOCKED(阻塞状态)、WAITING(无时间限制等待)、TIMED_WAITING(有时间限制等待)可以理解为三个原因线程导致睡眠状态。具体有哪些情况会导致线程从RUNNABLE状态过渡到这三种状态呢?这三个状态何时转换回RUNNABLE?NEW、TERMINATED和RUNNABLE状态如何转换?1.RUNNABLE和BLOCKED状态转换触发这种转换只有一种场景,即线程等待synchronized隐式锁。同步修饰的方法和代码块只允许一个线程同时执行,其他线程只能等待。在这种情况下,等待线程将从RUNNABLE状态切换到BLOCKED状态。而当等待线程获取到synchronized隐式锁后,又会从BLOCKED状态转为RUNNABLE状态。如果你熟悉操作系统线程的生命周期,你可能会有一个疑问:当一个线程调用一个阻塞的API时,它会转换到BLOCKED状态吗?在操作系统层面,线程会转入休眠状态,但在JVM层面,Java线程的状态不会改变,也就是说Java线程的状态会一直保持在RUNNABLE状态。JVM层面并不关心操作系统调度相关的状态,因为从JVM的角度来看,等待CPU使用权(此时操作系统层面处于可执行状态)和等待没有区别I/O(此时操作系统层面处于休眠状态),都在等待某个资源,所以都归入RUNNABLE状态。而我们通常所说的Java线程在调用阻塞API时会阻塞,这是指操作系统线程的状态,而不是Java线程的状态。2.RUNNABLE和WAITING之间的状态转换一般来说,有3种场景会触发这种转换。第一种场景,获取synchronized隐式锁的线程调用不带参数的Object.wait()方法。在第二种情况下,调用不带参数的Thread.join()方法。其中,join()是线程同步方法。例如,如果有一个线程对象线程A,当A.join()被调用时,执行这条语句的线程会等待线程A执行完毕,等待线程,其状态由RUNNABLE转变为WAITING。当thread线程A执行完毕后,等待它的线程又会从WAITING状态切换到RUNNABLE状态。在第三种情况下,调用LockSupport.park()方法。您可能对LockSupport对象有点陌生。其实Java并发包中的锁都是基于它实现的。调用LockSupport.park()方法,当前线程将被阻塞,线程状态由RUNNABLE变为WAITING。调用LockSupport.unpark(Threadthread)唤醒目标线程,目标线程的状态将从WAITING变为RUNNABLE。3、RUNNABLE和TIMED_WAITING之间的状态转换有五种场景可以触发这种转换:调用Thread.sleep(longmillis)方法并带超时参数;已获得同步隐式锁超时方法的线程调用带超时参数的Object.wait(longmillis)方法;使用超时参数调用Thread.join(longmillis)方法;使用超时参数调用LockSupport.parkNanos(Objectblocker,longdeadline)方法;使用超时参数调用LockSupport.parkUntil(longdeadline)方法。这里你会发现TIMED_WAITING和WAITING状态的区别在于触发条件多了超时参数。4、从NEW状态到RUNNABLE状态Java刚刚创建的Thread对象处于NEW状态,创建Thread对象主要有两种方式。一种是继承Thread对象,重写run()方法。示例代码如下:publicclassMyThreadextendsThread{@Overridepublicvoidrun(){//线程需要执行的代码System.out.println(Thread.currentThread().getName());}publicstaticvoidmain(String[]args){//创建线程对象MyThreadmyThread=newMyThread();}}另一种是实现Runnable接口,重写run()方法,将实现类作为参数创建Thread对象。示例代码如下:publicclassRunnerimplementsRunnable{@Overridepublicvoidrun(){//线程需要执行的代码System.out.println(Thread.currentThread().getName());}publicstaticvoidmain(String[]args){//创建线程对象Threadthread=newThread(newRunner());}}NEW线程不会被操作系统调度,所以不会执行。对于要执行的Java线程,它必须转换为RUNNABLE状态。从NEW状态切换到RUNNABLE状态非常简单,调用线程对象的start()方法即可。示例代码如下:publicclassRunnerimplementsRunnable{@Overridepublicvoidrun(){//线程需要执行的代码System.out.println(Thread.currentThread().getName());}publicstaticvoidmain(String[]args){//创建线程对象Threadthread=newThread(newRunner());//从NEW状态转换到RUNNABLE状态thread.start();}}5.从RUNNABLE到TERMINATED状态,执行完run()方法后,线程会自动切换到TERMINATED状态。当然,如果run()方法执行过程中抛出异常,线程也会终止。有时我们需要强行中断run()方法的执行。例如,run()方法访问一个非常慢的网络。我们等不及了。如果我们想终止它,我们应该怎么做呢?Java的Thread类中有一个stop()方法。但是,它已经被标记为@Deprecated,所以不建议使用它。正确的姿势其实是调用interrupt()方法。java.lang.Thread#stop()源代码:@Deprecatedpublicfinalvoidstop(){SecurityManagersecurity=System.getSecurityManager();if(security!=null){checkAccess();if(this!=Thread.currentThread()){security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);}}if(threadStatus!=0){resume();}stop0(newThreadDeath());}stop()和interrupt()方法之间的主要区别是什么?stop()方法它会真正杀死线程。如果线程持有ReentrantLock锁,线程stopped()不会自动调用ReentrantLock的unlock()释放锁,其他线程就永远没有机会获得ReentrantLock锁。危险的。因此,不推荐使用这种方法。类似的方法还有suspend()和resume()方法,同样不推荐使用。interrupt()方法只是通知线程该线程有机会执行一些后续操作,也可以忽略该通知。被中断的线程如何收到通知?一是异常,二是主动检测。当线程A处于WAITING或TIMED_WAITING状态时,如果其他线程调用线程A的interrupt()方法,线程A会回到RUNNABLE状态,线程A的代码会触发InterruptedException异常。上面我们提到,进入WAITING和TIMED_WAITING状态的触发条件都是调用wait()、join()、sleep()等方法。我们查看这些方法的签名,发现它们都抛出InterruptedException。这个异常的触发条件是:其他线程调用了本线程的interrupt()方法。当线程A处于RUNNABLE状态,阻塞在java.nio.channels.InterruptibleChannel上时,如果其他线程调用线程A的interrupt()方法,线程A会触发java.nio.channels.ClosedByInterruptException异常;当java.nio.channels.Selector开启时,如果其他线程调用线程A的interrupt()方法,线程A的java.nio.channels.Selector会立即返回。以上两种情况属于被中断的线程已经通过异常的方式得到通知。还有主动检测。如果线程处于RUNNABLE状态并且没有阻塞在I/O操作上,例如中断线程A,则线程A必须主动检测中断状态。如果其他线程调用了线程A的interrupt()方法,那么线程A就可以使用isInterrupted()方法来检测自己是否被中断了。总结本文介绍了操作系统中线程的生命周期,然后介绍了Java中线程的生命周期,并讲解了状态之间的转换。
