Java并发编程-Java 线程基本API

1.准备工作

加入依赖:

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>

 

logback.xml 配置如下

<?xml version="1.0" encoding="UTF-8"?>
<configuration
        xmlns="http://ch.qos.logback/xml/ns/logback"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://ch.qos.logback/xml/ns/logback logback.xsd">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date{HH:mm:ss} [%t] %logger - %m%n</pattern>
        </encoder>
    </appender>
    <logger name="c" level="debug" additivity="false">
        <appender-ref ref="STDOUT"/>
    </logger>
    <root level="ERROR">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

 

为了后面方便,加入此工具类

package cn.yutian.n2.util;

import java.util.concurrent.TimeUnit;

public class Sleeper {
    public static void sleep(int i) {
        try {
            TimeUnit.SECONDS.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void sleep(double i) {
        try {
            TimeUnit.MILLISECONDS.sleep((int) (i * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

线程是进程中的一个实体,线程本身是不会独立存在的。线程是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。在Java中,当我们启动main函数时其实就启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。

线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器(指向正在执行的指令指针)以及各自的生命周期,现代操作系统中一般不止一个线程在运行,当启动了一个 Java 虚拟机(JVM)时,从操作系统开始就会创建一个新的进程(JVM 进程),JVM 进程中将会派生或者创建很多线程。

 

2. Java 线程API介绍

1. 创建和运行线程

java 中有三种线程创建方式,分别为实现 Runnable 接口的 run 方法,继承 Thread重写 run 方法,使用 FutureTask 方式

方法一,继承 Thread重写 run 方法:

package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.ThreadTest")
public class ThreadTest {

    public static class Thread01 extends Thread{
        @Override
        public void run() {
            log.debug("hello");
        }
    }

    public static void main(String[] args) {
        Thread01 thread01 = new Thread01();
        thread01.start();
        log.debug("hello");
    }



}

需要注意的是:

  • 当创建完 thread 对象后该线程并没有被启动执行,直到调用了 start 方法后才真正启动了线程。
  • Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。
  • 当前线程直接使用this就可以了,无须使用Thread.currentThread()方法
  • 任务与代码没有分离,当多个线程执行一样的任务时需要多份代码

由于我们没有指定线程名,默认为main和Thread-01

更改名字
thread01.setName("T01");

匿名内部类写法:

package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.ThreadTest")
public class ThreadTest01 {


    public static void main(String[] args) {
        Thread thread01 = new Thread(){
            @Override
            public void run() {
                log.debug("hello");
            }
        };
        thread01.setName("T01");
        thread01.start();
        log.debug("hello");
    }

}
lamboda简化写法:
package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.ThreadTest")
public class ThreadTest01 {


    public static void main(String[] args) {
        Thread thread01 = new Thread(() -> log.debug("hello"));
        thread01.setName("T01");
        thread01.start();
        log.debug("hello");
    }

}

 

方法二,使用 Runnable 配合 Thread

package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.RunableTest")
public class RunableTest{

    public static class Thread01 implements Runnable{
        @Override
        public void run() {
            log.debug("hello");
        }
    }

    public static void main(String[] args) {
        Thread01 runable = new Thread01();
        new Thread(runable).start();
        new Thread(runable).start();
        log.debug("hello");
    }
}

当多个线程执行一样的任务时可以添加参数进行任务区分

第二个参数,给线程取名:

 

Runnable接口源码:

带有@FunctionalInterface的都可以使用lamboda简化

 

匿名内部类写法:

package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.RunableTest")
public class RunableTest01 {

    public static void main(String[] args) {

        Runnable runable = new Runnable(){
            @Override
            public void run() {
                log.debug("hello");
            }
        };
        new Thread(runable).start();
        new Thread(runable).start();
        log.debug("hello");
    }
}

 

lamboda简化写法:

package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.RunableTest")
public class RunableTest01 {

    public static void main(String[] args) {

        Runnable runable = () -> log.debug("hello");
        new Thread(runable).start();
        new Thread(runable).start();
        log.debug("hello");
    }
}

 

查看源码:

CTRL+ B,进入源码,可以看到传给了init方法

再次CTRL+ B,进入init方法,又是一个init方法

再次CTRL+ B,进入init

init中,至关重要的的一步,this.target = target

而这个成员变量在run方法,会用到,就和第一个继承 Thread重写 run 方法创建线程一样了

而同理,第一个继承 Thread重写 run 方法创建线程,因为重写了run方法了,就使用重写的run方法

 

方法三,FutureTask 配合 Thread

package com.yutian;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

@Slf4j(topic = "c.CallerTask")
public class CallerTask {
    public static class Thread01 implements Callable<String>{
        @Override
        public String call() throws Exception {
            log.debug("hello");
            return "Hello JUC";
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new Thread01());
        new Thread(futureTask).start();
        String result = futureTask.get();
        System.out.println(result);
        log.debug("hello");
    }
}

源码:

CTRL+ B,进入源码

往上拉,找到类,其实现了RunnableFuture

而RunnableFuture实现了Runnable,所以才可以将其像第二种方法一样,传给Thread

而实现的Future<V>接口可以返回结果:

 

package com.yutian;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

@Slf4j(topic = "c.CallerTask")
public class CallerTask01 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new Callable<String>(){
            @Override
            public String call() throws Exception {
                log.debug("hello");
                return "Hello JUC";
            }
        });
        new Thread(futureTask).start();
        String result = futureTask.get();
        System.out.println(result);
        log.debug("hello");
    }
}

 

2 .观察多个线程同时运行

对于线程来说,是交替执行的,没有谁先谁后,不由我们控制

package com.yutian;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.TestMultiThread")
public class TestMultiThread {

    public static void main(String[] args) {
        new Thread(() -> {
            while(true) {
                log.debug("running");
            }
        },"t1").start();
        new Thread(() -> {
            while(true) {
                log.debug("running");
            }
        },"t2").start();
    }
}
可以看到

 

 

3 .查看进程线程的方法

  • windows

任务管理器可以查看进程和线程数,也可以用来杀死进程

查看进程

tasklist

杀死进程

taskkill

查看java进程

tasklist | findstr java

 

  • linux

查看所有进程

ps -fe

查看所有java进程

ps -fe | grep java

查看某个进程(PID)的所有线程

ps -fT -p <PID> 

杀死进程

kill <PID>

动态采集线程

top

查看某个进程(PID)的所有线程

top -H -p <PID>

 

  • Java

命令查看所有 Java 进程

jps

查看某个 Java 进程(PID)的所有线程状态

jstack <PID>

jconsole来查看某个 Java 进程中线程的运行情况(图形界面)

jconsole:

  • 从Java5开始,在JDK中自带的java监控和管理控制台。
  • 用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具

官方教程:Using JConsole – Java SE Monitoring and Management Guide (oracle.com)

远程连接:

使用代码:
public class Thread01 {

    public static void main(String[] args) {
        new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"t1").start();
        new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"t2").start();
    }
}

将其上传到linux

javac Thread01.java

java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=是否认证 java类

java -Djava.rmi.server.hostname=192.168.188.201 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=12345 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false Thread01

 

 

 

 

4 .原理之线程运行

栈与栈帧:
Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

示例代码:

package cn.itcast.n3;

public class TestFrames {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                method1(20);
            }
        };
        t1.setName("t1");
        t1.start();
        method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object n = new Object();
        return n;
    }
}

debug代码,产生main栈帧

点击下一步

进入新方法,产生新栈帧

再点击下一步

进入新方法,产生新栈帧

再继续

注意,在java中,线程运行和就绪都是RUNNING

debug还可以切换线程: 

互不影响

 

线程上下文切换(Thread Context Switch):
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念,就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

 

5 .常见方法

 常见方法:

  • start():启动一个新线程,在新的线程运行run方法中的代码,start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateExceptionrun
  • run():新线程启动后会调用的方法。如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
  • join():等待线程运行结束
  • join(long n):等待线程运行结束,最多等待n 毫秒
  • getId():获取线程长整型的id。id 唯一
  • getName():获取线程名
  • setName(String):修改线程名
  • getPriority():获取线程优先级
  • setPriority(int):修改线程优先级。java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被 CPU 调度的机率
  • isInterrupted():判断是否被打断,不会清除打断标记
  • isAlive():线程是否存活(还没有运行完毕)
  • interrupt():打断线程,如果被打断线程正在 sleep, wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记
  • interrupted():static方法,判断当前线程是否被打断。会清除打断标记
  • currentThread() :static方法,获取当前正在执行的线程
  • sleep(long n):static方法,让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程
  • yield():static方法,提示线程调度器让出当前线程对CPU的使用。主要是为了测试和调试

 

6.start 与 run

调用 run

package cn.itcast.test;

import cn.itcast.Constants;
import cn.itcast.n2.util.FileReader;
import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test4")
public class Test4 {

    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
 
            }
        };

        t1.run();
        log.debug("do other things...");
    }
}

输出

程序仍在 main 线程运行.

将上述代码的 t1.run() 改为

t1.start();

注意start()只能调用一次

直接调用 run 是在主线程中执行了 run,没有启动新的线程
使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

 

7. sleep 与 yield

sleep

1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test6")
public class Test6 {

    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        log.debug("t1 state: {}", t1.getState());

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state: {}", t1.getState());
    }
}

 

2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException,所以我们要对其捕获。

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test7")
public class Test7 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("enter sleep...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
    }
}

3. 睡眠结束后的线程未必会立刻得到执行,要等待cpu.

4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

其实,它底层调用的也是Thread.sleep(),只是进行了换算。

 

yield

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

 

线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
  • setPriority(int):修改线程优先级。java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被 CPU 调度的机率

源码:

可以看到最大为10,最小为1,默认为5.

 

注意:

sleep 实现:在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序

package cn.itcast.test;

public class T11 {
    public static void main(String[] args) {
        while(true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 可以用 wait 或 条件变量达到类似的效果
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep 适用于无需锁同步的场景

 

8. join 方法详解

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        //t1.join();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
  • 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0

将注释去掉:

t1.join();

需要等待t1线程结束返回,才能继续运行main线程

最后输出r为10

注意:

线程A调用线程B的 join方法后会被阻塞,当其他线程调用了线程A的interrupt()方法中断了线程A时,线程A会抛出 InterruptedException 异常而返回。

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("t1开始");
            for (; ;) {
            }
        },"t1");
        final Thread mainthread = Thread.currentThread();
        Thread t2 = new Thread(() -> {
            log.debug("t2开始");
            sleep(1); 
            mainthread.interrupt();
            log.debug("t2结束");
        },"t1");
        log.debug("结束");
        t1.start();
        t2.start();
        t1.join();
    }
}

join(long n):等待线程运行结束,最多等待n 毫秒

 

9. interrupt 方法详解

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

  • void interrupt()方法:中断线程,例如,当线程 A运行时,线程 B 可以调用线程 A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程 A 实际并没有被中断,它会继续往下执行。如果线程 A 因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException 异常而返回。
  • boolean isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回 false.
  • boolean interrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false,与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志(意思是再次判断会为false),并且该方法是static方法,可以通过Thread类直接调用。在 interrupted()内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。
package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test12")
public class Test12 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true) {
                if(Thread.currentThread().isInterrupted()) {
                    log.debug("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
    }
}

 

还可以打断 park 线程

Park 和 Unpark 均是 LockSupport 类中的方法

//暂停当前线程 
LockSupport.park();
//恢复某个线程 
LockSupport.unpark(暂停线程对象);

注意:

  1. park 中的线程,处于 WAIT 状态
  2. unpark 既可以在 park 之前调用或之后调用,都是用来恢复某个线程的运行,简单的说,调用 unpark 后再调用 park 线程依然不会暂停。

interrupt() 方法可以打断 park 线程, 使其继续执行下去。

package com.dreams.t;
 
import lombok.extern.slf4j.Slf4j;
 
import java.util.concurrent.locks.LockSupport;

import static com.dreams.t.Sleeper.sleep;

@Slf4j(topic = "c.Test14")
public class Test14 {
 
    private static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                log.debug("park...");
                LockSupport.park();
                log.debug("打断状态:{}", Thread.currentThread().isInterrupted());

                LockSupport.park();
                log.debug("park...");
            }
        });
        t1.start();


        sleep(1);
        t1.interrupt();
 
    }

    private static void test4() {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                log.debug("park...");
                LockSupport.park();
                log.debug("打断状态:{}", Thread.interrupted());

                LockSupport.park();
                log.debug("park...");
            }
        });
        t1.start();


        sleep(1);
        t1.interrupt();
    }
 
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
}

如上,test3和test4两种不同状况,park 方法再次调用会失效,因为 park 线程只有打断状态为false才有效,已经调用了t1.interrupt()将状态设置为true了,所以只有Thread.interrupted()清空状态,重新变为false才能再次阻塞。

test3不会阻塞

test4则会

 

10 .不推荐的方法

不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁

  • stop()停止线程运行
  • suspend()挂起(暂停)线程运行
  • resume()恢复线程运行

 

已弃用

 

11 .主线程与守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test15")
public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
        log.debug("结束");
        }, "t1");
        // t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);
        log.debug("结束");
    }
}

会一直循环
而将注释去掉:

t1.setDaemon(true);

main线程结束,就结束了

注意:

  • 垃圾回收器线程就是一种守护线程,程序停止了,垃圾回收器线程也会停止。
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

 

12 .五种状态

从 操作系统 层面来描述的

 

  1. 初始状态(NEW):仅是在语言层面创建了线程对象,还未与操作系统线程关联,在没有start之前,该线程根本不存在,与用关键字new创建一个普通的Java对象没什么区别。
  2. 可运行状态(就绪状态)(RUNNABLE ):指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行,线程对象进入 RUNNABLE 状态必须调用 start 方法,具备执行的资格
  3. 运行状态(RUNNING ):指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会从运行状态转换至可运行状态,会导致线程的上下文切换,一个正在 RUNNING 状态的线程事实上也是RUNNABLE的,但是反过来则不成立。
  4. 阻塞状态(BLOCKED):如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入阻塞状态,等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至可运行状态,与可运行状态的区别是,对阻塞状态的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  5. 终止状态(TERMINATED):表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

 

13 .六种状态

这是从 Java API 层面来描述的
根据 Thread中的枚举类State ,分为六种状态。

 

 

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统层面的可运行状态、运行状态和阻塞状态(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
  • TERMINATED 当线程代码运行结束

 

假设有线程 Thread t

情况 1 :NEW –> RUNNABLE
当调用 t.start() 方法时,由 NEW –> RUNNABLE

 

情况 2 :RUNNABLE <–> WAITING
t 线程用 synchronized(obj) 获取了对象锁后
调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING
调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

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

 

情况 3: RUNNABLE <–> WAITING
当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING
注意是当前线程在t 线程对象的监视器上等待
t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE

 

情况 4: RUNNABLE <–> WAITING
当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING
调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –>
RUNNABLE

 

情况 5 :RUNNABLE <–> TIMED_WAITING
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

 

情况 6: RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING,注意是当前线程在t 线程对象的监视器上等待
  • 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从TIMED_WAITING –> RUNNABLE

 

情况 7: RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING
  • 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE

 

情况 8: RUNNABLE <–> TIMED_WAITING

  • 当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线程从 RUNNABLE –> TIMED_WAITING
  • 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从TIMED_WAITING–> RUNNABLE

 

情况 9 :RUNNABLE <–> BLOCKED

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

 

情况 10: RUNNABLE <–> TERMINATED
当前线程所有代码运行完毕,进入 TERMINATED

 

注意,在idea里显示名字有差异:

 

参考资料

黑马程序员深入学习Java并发编程:https://www.bilibili.com/video/BV16J411h7Rd

汪文君著《Java高并发编程详解》

翟陆续著《Java并发编程之美》

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇