Java多线程面试精选Java技术升华

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

2021-11-24  本文已影响0人  我犟不过你

一、线程的状态

1.1 操作系统层面

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

操作系统层面的线程状态.png

1.2 Java的Thread状态

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

public enum State {
    /**
     * 初始
     */
    NEW,
    /**
     * 可运行
     */
    RUNNABLE,
    /**
     * 阻塞
     */
    BLOCKED,
    /**
     * 等待
     */
    WAITING,
    /**
     * 超时等待
     */
    TIMED_WAITING,
    /**
     * 终止
     */
    TERMINATED;
}
JAVA Thread状态.png

二、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 其他中断

三、方法与状态转换

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

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() 时:

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() 时:

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

上一篇 下一篇

猜你喜欢

热点阅读