Java线程的几种状态

时间:2021-01-28 18:15:35

Java线程的几种状态

一:线程几种状态转换

1.       程序通过Thread t = newThread(),调用t.start()启动一个线程,使该线程进入可运行(Runnable)的状态。

2.       由JVM的决定去调度(Scheduler) 在可运行状态(Runnable)下的线程,使该线程处于运行 (Running) 状态,由于JVM的调度会出现不可控性,即不是优先级高的先被调用,可能先调用,也可能后调用的的情况。运行状态(Running)下,调用礼让yield()方法,可以使线程回到可运行状态(Runnable)下,再次JVM的调度(并不依赖优先级)。

3.       线程在Running的过程中可能会遇到阻塞(Blocked)情况

①.调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。

②.调用wait(),使该线程处于等待池(wait blockedpool),直到notify()/notifyAll(),线程被唤醒被放到锁池(lock blocked pool),释放同步锁使线程回到可运行状态(Runnable)

③.对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool),同步锁被释放进入可运行状态(Runnable)。

4.       线程run()运行结束或异常退出,线程到达死亡状态(Dead)

 

在Java中创建线程有两种方法:使用Thread类和使用Runnable接口。
要注意的是Thread类也实现了Runnable接口,因此,从Thread类继承的类的实例也可以作为target传入这个构造方法。可通过这种方法实现多个线程的资源共享。

二:线程的生命周期:

新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存 就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权 

运行状态:处于这个状态的线程占用CPU,执行程序的代码 
阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。 
阻塞状态分为三种情况: 
1、 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中 
2、 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。 
3、 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。

2.1、创建并运行线程
    当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。

2.2、挂起和唤醒线程
一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sleep。在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。
虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为deprecated(*)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。下面的代码演示了sleep、suspend和resume三个方法的使用。

2.3、终止线程的三种方法
有三种方法可以使终止线程。
1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3.  使用interrupt方法中断线程。

三:线程的几个方法:
join():等待此线程死亡后再继续,可使异步线程变为同步线程
interrupt():中断线程,被中断线程会抛InterruptedException

线程通信:wait(),notify()   - 典型应用:放取鸡蛋
wait() 等待获取锁:

表示等待获取某个锁

执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒 
notify() 执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。使线程由阻塞队列进入就绪状态

sleep():让当前正在执行的线程休眠,有一个用法可以代替yield函数——sleep(0)
yield():暂停当前正在执行的线程对象,并执行其他线程。也就是交出CPU一段时间

sleep和yield区别:
1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。 
2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。 
3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常 
4、sleep()方法比yield()方法具有更好的移植性 

如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一:
1、 调整各个线程的优先级 
2、 让处于运行状态的线程调用Thread.sleep()方法 
3、 让处于运行状态的线程调用Thread.yield()方法 
4、 让处于运行状态的线程调用另一个线程的join()方法

首先,wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法。

(1).常用的wait方法有wait()wait(long timeout):
    void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 
    void wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
    wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用。
    wait()和notify()因为会对对象的“锁标志”进行操作,所以它们必须在synchronized函数或synchronized  block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运 行时会发生IllegalMonitorStateException的异常。

(2).Thread.sleep(long millis),必须带有一个时间参数
    sleep(long)使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
    sleep(long)可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;
    sleep(long)是不会释放锁标志的。

(3).yield()没有参数。
    sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
    yield()也不会释放锁标志。

    实际上,yield()方法对应了如下操作: 先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。

    sleep方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程些时获得CPU占有权。 在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

    yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。所以yield()只能使同优先级的线程有执行的机会。

四:volitile 语义:
volatile相当于synchronized的弱实现,也就是说volatile实现了类似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其他的线程。
volatile包含以下语义:
(1)Java 存储模型不会对valatile指令的操作进行重排序:这个保证对volatile变量的操作时按照指令的出现顺序执行的。
(2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其他对CPU不可见的地方,每次总是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程总是可见的,并且不是使用自己线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操作后,其后的任何读操作理解可见此写操作的结果。
尽管volatile变量的特性不错,但是volatile并不能保证线程安全的,也就是说volatile字段的操作不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程能够理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!

五:数据同步:

线程同步的特征: 
1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制) 
2、 每个对象都有唯一的同步锁 
3、 在静态方法前面可以使用synchronized修饰符。 
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。 
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。