JUC之线程进程基础

文章目录

  • 1、进程和线程
    • 1.1、进程
    • 1.2、线程
    • 1.3、进程和线程之间的对比
  • 2、串行和并行
    • 2.1、概述
    • 2.2、串行并行之间的对比
    • 2.3、应用
      • 2.3.1、异步调用
      • 2.3.2、并行执行
  • 3、Java线程
    • 3.1、线程的创建
      • 3.1.1、Thread
      • 3.1.2、Runnable
      • 3.1.3、FutureTask
      • 3.1.4、Runnable和Thread之间的关系
    • 3.2、线程运行的原理
    • 3.3、线程上下文切换
  • 4、常用方法
    • 4.1、run和start
    • 4.2、getState
    • 4.3、sleep
    • 4.4、interrupt
      • 4.4.1、打断正在睡眠的线程
      • 4.4.2、打断正常运行的线程
      • 4.4.3、打断park线程
    • 4.5、yield
    • 4.6、setPriority
    • 4.7、join
    • 4.8、过时方法
  • 5、主线程和守护线程
  • 6、线程的状态
    • 6.1、五种状态
    • 6.2、六种状态
    • 6.3、线程状态切换
      • 6.3.1、NEW -> RUNNABLE
      • 6.3.2、RUNNABLE -> WAITING
      • 6.3.3、WAITING -> RUNNABLE
      • 6.3.4、RUNNABLE -> TIMED_WAITING
      • 6.3.4、TIMED_WAITING -> RUNNABLE
      • 6.3.5、RUNNABLE -> BLOCKED
      • 6.3.6、BLOCKED -> RUNNABLE
      • 6.3.7、RUNNABLE -> TERMINATED
  • *7、应用-统筹

1、进程和线程

1.1、进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

1.2、线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

1.3、进程和线程之间的对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPCInter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

2、串行和并行

2.1、概述

  • 单核 cpu 下,线程实际还是串行执行 的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给

    不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是 同时运行的 。总结为一句话就是: 微观串行,宏观并行 ,

    一般会将这种线程轮流使用 CPU 的做法称为并发(concurrent)

  • 多核 cpu下,每个核(core) 都可以调度运行线程,这时候线程可以是并行的。

2.2、串行并行之间的对比

  • 并发(concurrent)是同一时间应对(dealing with)多件事情的能力

  • 并行(parallel)是同一时间动手做(doing)多件事情的能力

2.3、应用

2.3.1、异步调用

以调用方来看,如果:

  • 需要等待结果返回,才能继续运行就是同步
FileReader.read("xxxx");
log.debug("do other things ...");
  • 不需要等待结果返回,就能继续运行就是异步
new Thread(() -> FileReader.read("xxxx")).start();
log.debug("do other things ...");

对于上述两段代码而言,可以明显感觉到,对于同步调用而言,某一个方法如果执行时间过长,特别地,IO 操作不占用 cpu,那么这个线程将会什么都

做不了傻傻的等着,使得后面的代码跟着都执行不了,但是如果使用异步调用,那么目标方法将会使用其他线程执行,其执行不再阻塞当前线程,使得当前线

程可以继续向下执行

2.3.2、并行执行

某四核 CPU 场景下,需要执行下面三个计算,最后再将三个计算结果汇总

计算 1 花费 10 ms
计算 2 花费 11 ms
计算 3 花费 9 ms
汇总需要 1 ms
  • 串行执行,简单明了,10 + 11 + 9 + 1 = 31ms

  • 并行执行,核心1使用一个线程执行计算1,核心2使用一个线程执行计算2,核心3使用一个线程执行计算3,三者总运行时间只会取决于执行时间最长的计算

    2,也就是11ms,最后核心4使用一个线程汇总三个结果,共用时11 + 1 = 12ms

  • 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用 cpu ,不至于一个线程总占用 cpu,别的线程没

    法干活

  • 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的

    • 有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任务都能拆分
    • 也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义

3、Java线程

3.1、线程的创建

3.1.1、Thread

// 构造方法的参数是给线程指定名字,推荐
Thread t = new Thread("t") {
    @Override
    // run 方法内实现了要执行的任务
    public void run() {
        log.debug("hello");
    }
};
t.start();

3.1.2、Runnable

  • 一个 Runnable 表示可运行的任务(线程要执行的代码)
// 创建任务对象
Runnable task = new Runnable() {
    @Override
    public void run() {
        log.debug("hello");
    }
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t = new Thread(task, "t");
t2.start();

3.1.3、FutureTask

  • FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况
// 创建任务对象
FutureTask<Integer> task = new FutureTask<>(() -> {
    log.debug("hello");
    return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task, "t").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task.get();
log.debug("结果是:{}", result);

3.1.4、Runnable和Thread之间的关系

创建 Thread 的时候,如果传入一个 Runnable 对象,那么 Thread 类会将当前的 Runnable 对象赋值给自己的一个名为 target 的成员变量中

JUC之线程进程基础_第1张图片

当调用 Thread 类中的 start() 方法后,操作系统会启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 cpu 时间片,就开始执行 run() 方法

JUC之线程进程基础_第2张图片

这里方法 run() 称为线程体,它包含了要执行的这个线程的内容,run() 方法运行结束,此线程随即终止。

如果我们传入的不是 Runnable 对象,而是自己实现其 run() 方法,那么自然再线程得到 cpu 时间片后,就会走我们自己实现的 run() 方法

3.2、线程运行的原理

JVM 由栈,堆,方法区所组成,其中栈内存便是给线程使用的,每个线程启动后,JVM 都会为其分配一块栈内存

  • 每一个栈由多个栈帧(Frame)组成,对应每一次方法的调用所占用的内存
  • 当前正在执行的方法所处的栈帧,我们称之为活动栈帧,每一个线程有且仅有一个活动栈帧
/**
 * @author PengHuanZhi
 * @date 2021年12月08日 18:41
 */
public class Frames {
    public static void main(String[] args) throws InterruptedException {
        method1();
    }

    private static void method1() {
        int i = method2(1, 2);
        System.out.println(i);
    }

    private static int method2(int a, int b) {
        int c = a + b;
        return c;
    }
}

观察调试控制台

JUC之线程进程基础_第3张图片

图中的 Frames 便是对应于咱们的 Java 虚拟机栈帧,当前代码执行到 Main 方法,还未进入 method1 方法,所以栈帧集合只有一个 main 方法,现在进入 method1,再次观察

JUC之线程进程基础_第4张图片

同理,再次进入 method2

JUC之线程进程基础_第5张图片

method2 方法中出来,再观察,可以发现,method2 被弹出去啦

JUC之线程进程基础_第6张图片

3.3、线程上下文切换

线程上下文切换指的是因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完

  • 垃圾回收

  • 有更高优先级的线程需要运行

  • 线程自己调用了 sleepyieldwaitjoinparksynchronizedlock 等方法

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

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等

  • 频繁的线程上下文切换会影响性能

4、常用方法

方法名 static 功能说明 注意
start() 启动一个新线程,在新的线程运行run方法中的代码 start方法只是让线程进入就绪,里面的代码不一定立刻运行(CUP的时间片还没有分给他)。每个线程对象的start方法只能调用一次,如果调用多次会出现IllegalThreadStateException
run() 新线程启用后会调用的方法 如果在构造Thread对象时传递了Runnable参数,则线程启动后调用Runnable中的run方法,否则默认不执行任何操作。但可以穿件Thread的子类对象,来覆盖默认行为
join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等待n毫秒
getId() 获取线程长整型的id id唯一
getName() 获取线程名
setName(String) 修改线程名
getPriority() 获取线程优先级
getPriority(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 的时间片给其它 线程
yield() static 提示线程调度器 让出当前线程对 CPU的使用 主要是为了测试和调试

4.1、run和start

对于这俩方法,上面在讲Runnable和Thread之间的关系有提到,这里不再赘述了

4.2、getState

此方法可获取当前线程的状态,Java 中线程状态是用 6 个 enum 表示,分别为:

  • NEW:新创建了一个线程对象,但还没有调用start()方法。

  • RUNNABLEJAVA 线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

  • BLOCKED:阻塞,表示线程阻塞于锁

  • WAITING:等待,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

  • TIMED_WAITING:有限等待状态(有时限,对应sleep方法的睡眠时间)

  • TERMINATED:终止

简单看一下就可以了

Thread t = new Thread(() -> System.out.println("invoke..."), "t");
System.out.println(t.getState());
t.start();
System.out.println(t.getState());

JUC之线程进程基础_第7张图片

同一个线程不可被多次调用,否则会抛出错误的线程状态异常 IllegalThreadStateException

4.3、sleep

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(有限等待状态)

  • 睡眠结束后的线程未必会立刻得到执行

  • 建议用 TimeUnitsleep 代替 Threadsleep 来获得更好的可读性

@SneakyThrows
public static void main(String[] args) {
    Thread t = new Thread(() -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            //Do Nothing
        }
    }, "t");
    System.out.println(t.getState());
    t.start();
    Thread.sleep(1000);
    System.out.println(t.getState());
}

JUC之线程进程基础_第8张图片

4.4、interrupt

4.4.1、打断正在睡眠的线程

其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException 从而使得睡眠线程重新执行

Thread t = new Thread(() -> {
    log.info("sleep...");
    try {
        Thread.sleep(5000); // wait, join
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "t");
t1.start();
Thread.sleep(1000);
log.info("interrupt");
t1.interrupt();
log.info("打断标记:{}", t.isInterrupted());
Thread.sleep(1000);
log.info("打断标记:{}", t.isInterrupted());

JUC之线程进程基础_第9张图片

对于 sleep、wait、join 的线程,将其打断后,线程会重新进入一个 WAITING 状态,打断状态在线程变为 WAITING 后会被清空

4.4.2、打断正常运行的线程

其他线程可以使用 interrupt 方法打断一个正常运行的线程,正常运行的线程还可以分为阻塞状态的线程和正在执行的线程

Thread t = new Thread(() -> {
    while (true) {
        boolean interrupted = Thread.currentThread().isInterrupted();
        if (interrupted) {
            log.info("被打断了, 退出循环");
            break;
        }
    }
}, "t");
t.start();

Thread.sleep(1000);
log.info("interrupt");
t.interrupt();

image-20220207102725701

4.4.3、打断park线程

LockSupport 中的 park() 方法也可以使当前线程停下来

Thread t = new Thread(() -> {
    log.info("park...");
    LockSupport.park();
    log.info("unpark...");
    log.info("打断状态:{}", Thread.currentThread().isInterrupted());
    log.info("unpark...");
}, "t");
t.start();

TimeUnit.SECONDS.sleep(1);
t.interrupt();

JUC之线程进程基础_第10张图片

park 方法具有一个特点,那就是当前已经被 park 后的线程被打断后,再次执行 park 方法,当前线程便停不下来了

park 方法失效的原因便是它只对打断状态为 false 的线程生效,第一次被打断后,打断状态变为 true ,如果还想要 park 方法生效,可以这样做使用

interrupted 方法重置打断状态

Thread t = new Thread(() -> {
    log.info("park...");
    LockSupport.park();
    log.info("unpark...");
    log.info("打断状态:{}", Thread.currentThread().isInterrupted());
    Thread.interrupted();
    log.info("打断状态:{}", Thread.currentThread().isInterrupted());
    LockSupport.park();
    log.info("unpark...");
}, "t");
t.start();

TimeUnit.SECONDS.sleep(1);
t.interrupt();

JUC之线程进程基础_第11张图片

4.5、yield

本意是让出的意思

  • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程

  • 具体的实现依赖于操作系统的任务调度器,如果当前系统资源很充裕,那么操作系统的任务调度器还是会将时间片交由当前线程执行

区别于 sleep,执行 yield 方法的线程,下一时间片仍然有机会执行,但是执行 sleep 方法的线程,只能等若干时间后才有机会执行,而且还不是立刻执行

Runnable task1 = () -> {
    int count = 0;
    for (; ; ) {
        System.out.println("---->1 " + count++);
    }
};
Runnable task2 = () -> {
    int count = 0;
    for (; ; ) {
        System.out.println("---->2 " + count++);
    }
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.start();
t2.start();

JUC之线程进程基础_第12张图片

t1 每次循环添加一个 yield 方法

Runnable task1 = () -> {
    int count = 0;
    for (; ; ) {
        Thread.yield();
        System.out.println("---->1 " + count++);
    }
};

再次执行

JUC之线程进程基础_第13张图片

4.6、setPriority

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
Runnable task1 = () -> {
    int count = 0;
    for (; ; ) {
        System.out.println("---->1 " + count++);
    }
};
Runnable task2 = () -> {
    int count = 0;
    for (; ; ) {
        System.out.println("---->2 " + count++);
    }
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();

以上代码,笔者设备检测不出来太大差异,读者如果需要证明,可以尝试将上述代码运行在一个单核 CPU 的虚拟机上,最终结果应该是 t2 的执行次数要高于 t1

4.7、join

阻塞等待目标线程运行结束

private static int r = 0;

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        try {
            System.out.println("开始");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        r = 10;
        System.out.println("结束");
    });
    t1.start();
    System.out.println("结果为:" + r);
}

打印结果为

结果为:0
开始
结束

具体原因为

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

解决办法有两种

  • 主线程使用 sleep,但是不推荐这样使用,因为 t1 执行时间往往是不固定的
  • 主线程使用 t1.join(),主线程执行到这一步后,就会阻塞住,直到 t1 执行完毕,join 方法可以添加一个超时参数,如果超时主线程取消阻塞继续向下执行
private static int r = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            System.out.println("开始");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        r = 10;
        System.out.println("结束");
    });
    t1.start();
    t1.join();
    System.out.println("结果为:" + r);
}

打印结果为

开始
结束
结果为:10

4.8、过时方法

这些方法已过时,容易破坏同步代码块,造成线程死锁

方法名 功能说明
stop() 停止线程运行
suspend() 挂起(暂停)线程运行
resume() 恢复线程运行
  • stop 可以采用终止模式之两阶段终止来替代
  • suspend 方法可以使用 Object.wait 方法来更好的挂起线程
  • resume 也可以使用 Object.notify 方法来更好的唤醒线程

5、主线程和守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。

Thread t = new Thread(() -> {
    while (true) {
        if (Thread.currentThread().isInterrupted()) {
            break;
        }
    }
    log.info("结束");
}, "t");
t.start();

Thread.sleep(1000);
log.info("结束");

JUC之线程进程基础_第14张图片

有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束,只需要使用 setDaemon(true) 就可以将一个线程设置为守护线程。

Thread t = new Thread(() -> {
    while (true) {
        if (Thread.currentThread().isInterrupted()) {
            break;
        }
    }
    log.info("结束");
}, "t");
t.setDaemon(true);
t.start();

Thread.sleep(1000);
log.info("结束");

JUC之线程进程基础_第15张图片

  • 垃圾回收器线程就是一种守护线程

  • Tomcat 中的 Acceptor (接受请求)和 Poller (分发请求)线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求

6、线程的状态

6.1、五种状态

从操作系统层面来看,线程有五种状态

  • 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 就绪状态(可运行状态):指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 运行状态:指获取了 CPU 时间片运行中的状态,当 CPU 时间片用完,会转换至就绪状态,线程会进行上下文切换
  • 阻塞状态:阻塞状态的线程,只要不被唤醒,调度器将永远不会考虑调度他们,比如调用了 BIO 读写文件,由于 IO 操作不会使用到 CPU 所以当前线程会进行上下文切换,进入阻塞状态,只有当 IO 操作结束,操作系统才会唤醒它,然后转换至就绪状态
  • 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

6.2、六种状态

JAVA API 层面描述,线程有六种状态,详情查阅 Thread.State

public enum State {
    /**
     * 尚未启动的线程的线程状态
     */
    NEW,

    /**
     * 可运行线程的线程状态。处于可运行状态的线程正在 Java 虚拟机中执行,
     * 但它可能正在等待来自操作系统的其他资源,例如处理器。
     */
    RUNNABLE,

    /**
     * 线程阻塞等待锁的线程状态。处于阻塞状态的线程正在等待解锁然后进入同步块方法
     * 或调用 Object.wait 之后重新进入同步块方法。
     */
    BLOCKED,

    /**
     * 等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
     *    Object.wait
     *    Thread.join
     *    LockSupport.park
     *
     * 处于等待状态的线程正在等待另一个线程执行特定操作。
     *
     * 例如:
     *    在一个对象上调用了 Object.wait() 的线程正在等待另一个线程调用 
     * Object.notify() 或 Object.notifyAll() 在那个对象上。
     *
     *    已调用 Thread.join() 的线程正在等待指定线程终止。
     */
    WAITING,

    /**
     * 具有指定等待时间的等待线程的线程状态。由于以指定的等待时间调用以下方法之一,线程处于定时等待状态:
     *    Thread.sleep(long timeout)
     *    Object.wait(long timeout)
     *    Thread.join(long timeout)
     *    LockSupport.parkNanos(long timeout)
     *    LockSupport.parkUntil(long timeout)
     */
    TIMED_WAITING,

    /**
     * 已终止线程的线程状态。线程已完成执行。
     */
    TERMINATED;
}
  • NEW:线程刚被创建,但是还没有调用 start() 方法

  • RUNNABLE:当调用了线程的 start() 方法之后,Java API 层面的 RUNNABLE 状态涵盖了操作系统层面的 【就绪状态】、【运行状态】和【阻塞状

    态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)

  • BLOCKED WAITING TIMED_WAITING:都是 Java API 层面对【阻塞状态】的细分

  • TERMINATED:当线程代码运行结束

示例:

Thread t1 = new Thread("t1") {
    @Override
    public void run() {
        log.info("running...");
    }
};

Thread t2 = new Thread("t2") {
    @Override
    public void run() {
        while (true) { // runnable

        }
    }
};
t2.start();

Thread t3 = new Thread("t3") {
    @Override
    public void run() {
        log.info("running...");
    }
};
t3.start();

Thread t4 = new Thread("t4") {
    @Override
    public void run() {
        synchronized (Test.class) {
            try {
                Thread.sleep(1000000); // timed_waiting
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
t4.start();

Thread t5 = new Thread("t5") {
    @Override
    public void run() {
        try {
            t2.join(); // waiting
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};
t5.start();

Thread t6 = new Thread("t6") {
    @Override
    public void run() {
        synchronized (Test.class) { // t4已经拿到锁没有释放,所以这里会blocked
            try {
                Thread.sleep(1000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
t6.start();

try {
    Thread.sleep(500);
} catch (InterruptedException e) {
    e.printStackTrace();
}
log.info("t1 state {}", t1.getState());
log.info("t2 state {}", t2.getState());
log.info("t3 state {}", t3.getState());
log.info("t4 state {}", t4.getState());
log.info("t5 state {}", t5.getState());
log.info("t6 state {}", t6.getState());

JUC之线程进程基础_第16张图片

6.3、线程状态切换

6.3.1、NEW -> RUNNABLE

  • 调用 start 方法

6.3.2、RUNNABLE -> WAITING

  • 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait() 方法时
  • 当前线程调用 t.join() 方法时,注意是当前线程在 t 线程对象的监视器上等待
  • 当前线程调用 LockSupport.park()

6.3.3、WAITING -> RUNNABLE

  • t 线程运行结束
  • 线程用 synchronized(obj) 获取了对象锁后,调用 obj.notify()obj.notifyAll()t.interrupt() 时,且竞争锁成功(否则变为 BLOCKED
  • 调用 LockSupport.unpark(目标线程)

6.3.4、RUNNABLE -> TIMED_WAITING

  • 线程用 synchronized(obj) 获取了对象锁后,调用 obj.wait(long n) 方法时
  • 当前线程调用 t.join(long n) 方法时,注意是当前线程在 t 线程对象的监视器上等待
  • 当前线程调用 Thread.sleep(long n)
  • 当前线程调用 LockSupport.parkNanos(long nanos)LockSupport.parkUntil(long millis)

6.3.4、TIMED_WAITING -> RUNNABLE

  • t 线程运行结束
  • 线程等待时间超过了 n 毫秒,或调用 obj.notify()obj.notifyAll()t.interrupt() 时,且竞争锁成功(否则变为 BLOCKED
  • 调用 LockSupport.unpark(目标线程)

6.3.5、RUNNABLE -> BLOCKED

  • 线程用 synchronized(obj) 获取了对象锁时如果竞争失败

6.3.6、BLOCKED -> RUNNABLE

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

6.3.7、RUNNABLE -> TERMINATED

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

*7、应用-统筹

附:华罗庚《统筹方法》

统筹方法,是一种安排工作进程的数学方法。它的实用范围极广泛,在企业管理和基本建设中,以及关系复杂的科研项目的组织与管理中,都可以应用。

怎样应用呢?主要是把工序安排好。

比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么办?

  • 办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开了,泡茶喝。
  • 办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡茶喝。
  • 办法丙:洗净水壶,灌上凉水,放在火上,坐待水开;水开了之后,急急忙忙找茶叶,洗茶壶茶杯,泡茶喝。

哪一种办法省时间?我们能一眼看出,第一种办法好,后两种办法都窝了工。

这是小事,但这是引子,可以引出生产管理等方面有用的方法来。

水壶不洗,不能烧开水,因而洗水壶是烧开水的前提。没开水、没茶叶、不洗茶壶茶杯,就不能泡茶,因而这些又是泡茶的前提。它们的相互关系,可以用下图来表示:

洗水壶 1分钟
烧开水 15分钟
泡茶
洗茶壶 1分钟
洗茶杯 2分钟
拿茶叶 1分钟

从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大可利用“等水开”的时间来做。

是的,这好像是废话,卑之无甚高论。有如走路要用两条腿走,吃饭要一口一口吃,这些道理谁都懂得。但稍有变化,临事而迷的情况,常常是存在的。在近代工业的错综复杂的工艺过程中,往往就不是像泡茶喝这么简单了。任务多了,几百几千,甚至有好几万个任务。关系多了,错综复杂,千头万绪,往往出现“万事俱 备,只欠东风”的情况。由于一两个零件没完成,耽误了一台复杂机器的出厂时间。或往往因为抓的不是关键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。 洗茶壶,洗茶杯,拿茶叶,或先或后,关系不大,而且同是一个人的活儿,因而可以合并成为:

洗茶壶 1分钟
烧开水 15分钟
洗茶壶,洗茶杯,拿茶叶 4分钟
泡茶

看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。 这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接 解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的。

其实文章已经分析的很清楚了,代码实现也极其简单

Thread t1 = new Thread(() -> {
    log.info("洗水壶");
    try {
        TimeUnit.SECONDS.sleep(1);
        log.info("烧开水");
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, "老王");

Thread t2 = new Thread(() -> {
    log.info("洗茶壶");
    try {
        TimeUnit.SECONDS.sleep(1);
        log.info("洗茶杯");
        TimeUnit.SECONDS.sleep(2);
        log.info("拿茶叶");
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    log.info("泡茶");
}, "小王");
t1.start();
t2.start();

JUC之线程进程基础_第17张图片

你可能感兴趣的:(JUC,java,面试,开发语言,juc)