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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容