java并发编程(三)java线程状态与方法

一、线程的状态

1.1 操作系统层面

在操作系统层面有五种状态:

操作系统层面的线程状态.png
  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态。当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

1.2 Java的Thread状态

Thread的状态,是一个enum,有六种状态,如下所示:

public enum State {
    /**
     * 初始
     */
    NEW,
    /**
     * 可运行
     */
    RUNNABLE,
    /**
     * 阻塞
     */
    BLOCKED,
    /**
     * 等待
     */
    WAITING,
    /**
     * 超时等待
     */
    TIMED_WAITING,
    /**
     * 终止
     */
    TERMINATED;
}
JAVA Thread状态.png
  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,在后面第三节(方法与状态转换)会讲解
  • TERMINATED 当线程代码运行结束

二、Thread的常用方法

2.1 常用方法

方法名 static 功能说明 注意
start() 启动一个线程,线程当中运行run()方法中的代码 start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException
run() 线程启动后调用的方法 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为;

class ExtendThread extends Thread {
   @Override
   public void run() {
    System.out.println("继承Thread类方式");
   }
}
join() 等待当前线程执行结束 在当前执行线程a中,另一个线程b调用该方法,则线程a进入WAITING状态,直到线程b执行完毕,线程a继续执行

原理:调用者轮询检查线程 alive 状态

等价于下面的代码:
synchronized (t1) {
   // 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束
      while (t1.isAlive()) {
      t1.wait(0);
   }
}
join(long n) 等待当前线程运行结束,最多等待 n毫秒
getId() 获取线程长整型的 id 唯一id
getName() 获取线程名称
setName(String) 修改线程名称
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率
getState() 获取线程状态 Java 中线程状态是用 6 个 enum 表示,分别为:
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED
isInterrupted() 判断是否被打断 不会清除 打断标记
isAlive() 判断线程是否存活(是否运行完毕)
interrupt() 打断线程 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出InterruptedException,并清除 打断标记 ;

如果打断的正在运行的线程,则会设置 打断标记 ;

park 的线程被打断,也会设置 打断标记。
interrupted() static 判断当前线程是否被打断 会清除 打断标记
currentThread() static 获取当前正在执行的线程
sleep(long n) static 让当前执行的线程休眠n毫秒,休眠时让出 cpu的时间片给其它线程
yied() static 提示线程调度器让出当前线程对CPU的使用 主要是为了测试和调试

2.2 sleep和yied

2.2.1 sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性(内部也是Thread.sleep)
TimeUnit.SECONDS.sleep(5);

2.2.2 yied

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

2.3 interrupt 方法详解

线程的Thread.interrupt()方法是中断线程,将会设置该线程的中断状态,即设置为true。

其作用仅仅而已,线程关闭还是继续执行业务进程应该由我们的程序自己进行判断。

针对不同状态下的线程进行中断操作,会有不一样的结果:

2.3.1 中断wait() 、join()、sleep()

如果此线程在调用Object类的wait() 、 wait(long)或wait(long, int)方法或join() 、 join(long) 、 join(long, int)被阻塞、 sleep(long)或sleep(long, int) ,此类的方法,则其中断状态将被清除并收到InterruptedException 。

以sleep举例:

    public static void main(String[] args) {

        Thread t = new Thread(() -> {
            System.out.println("打断状态:" + Thread.currentThread().isInterrupted());
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("打断后的状态:" + Thread.currentThread().isInterrupted());
        });
        t.start();
        t.interrupt();
        System.out.println("打断状态:" + t.isInterrupted());
    }

结果:

打断状态:true
打断状态:true
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.cloud.bssp.thread.InterruptTest.lambda$main$0(InterruptTest.java:18)
    at java.lang.Thread.run(Thread.java:748)

2.3.2 中断正常线程

正常线程将会被设置中断标记位,我们可以根据该标记位判断线程如何执行,如下所示:

    /**
     * 中断正常线程
     *
     * @param args
     */
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("中断状态:" + Thread.currentThread().isInterrupted());
                    break;
                }
            }
        });
        t.start();
        t.interrupt();
    }

结果:

中断状态:true

2.3.3 中断park线程

不会使中断状态清除。

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("park");
                LockSupport.park();
                System.out.println("unpark");
                System.out.println("中断状态:" + Thread.currentThread().isInterrupted());
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
        });
        t.start();
        TimeUnit.SECONDS.sleep(1);
        t.interrupt();
    }

结果:

park
unpark
中断状态:true

如果在park之前,线程已经是中断状态了,则会使park失效,如下所示,除了首次park成功能成功,被中断后,后面的park都失效了:

   /**
     * 中断park
     *
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("打断状态:" + Thread.currentThread().isInterrupted());
                System.out.println("park..." + i);
                LockSupport.park();
            }
        });
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
    }

结果:

打断状态:false
park...0
打断状态:true
park...1
打断状态:true
park...2
打断状态:true
park...3
打断状态:true
park...4

可以 Thread.interrupted() 方法去除中断标记:

2.3.5 不推荐使用的方法

方法名称 描述
stop() 停止线程运行。不安全的,并将会在未来版本删除
suspend() 挂起(暂停)线程运行,此方法已被弃用,因为它本质上容易死锁
resume() 恢复线程运行。此方法仅用于suspend ,已被弃用,因为它容易死锁

2.3.4 其他中断

  • 如果此线程在InterruptibleChannel的 I/O 操作中被阻塞,则通道将关闭,线程的中断状态将被设置,并且线程将收到java.nio.channels.ClosedByInterruptException 。

  • 如果该线程在java.nio.channels.Selector被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作中返回,可能具有非零值,就像调用了选择器的wakeup方法。

三、方法与状态转换

如下图所示,线的右侧表示执行的方法:

image.png

下面具体分析方法和状态转换,假设有一个线程Thread t:

1.NEW --> RUNNABLE

执行t.start()

2.RUNNABLE <--> WAITING

此种状态转换分三种情况:
1)t 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING

调用 obj.notify() , obj.notifyAll() , t.interrupt() 时:

  • 竞争锁成功,t 线程从 WAITING --> RUNNABLE
  • 竞争锁失败,t 线程从 WAITING --> BLOCKED

2)当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING

注意是当前线程在t 线程对象的监视器上等待

当前线程会等到t执行结束后或调用了当前线程的 interrupt() 时,WAITING --> RUNNABLE。

3)当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING

调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE

3.RUNNABLE <--> TIMED_WAITING

此种状态转换分四种情况:

1) t 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING

t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时:

  • 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE
  • 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED

2)当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING

注意是当前线程在t 线程对象的监视器上等待

当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从TIMED_WAITING --> RUNNABLE

3)当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING

当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE

4)当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE --> TIMED_WAITING

调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从
TIMED_WAITING--> RUNNABLE

4.RUNNABLE <--> BLOCKED

t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED

持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED

5.RUNNABLE <--> TERMINATED

当前线程所有代码运行完毕,进入 TERMINATED

你可能感兴趣的:(java并发编程(三)java线程状态与方法)