文章目录

  • 1. 线程状态
  • 1.1 Java线程六种状态及转换
  • 1.2 操作系统层次的五种状态
  • 2. 线程池(七大核心参数)
  • 3. wait vs sleep
  • 3.1 wait() 必须配合锁一起使用
  • 3.2 wait 方法执行后会释放锁 sleep则不会
  • 3.3 interrupt()方法可以提前唤醒sleep线程和wait线程



1. 线程状态

要求

  • 掌握 Java 线程六种状态
  • 掌握 Java 线程状态转换
  • 能理解五种状态与六种状态两种说法的区别

1.1 Java线程六种状态及转换

java await 加时间_java


分别是

  • 新建
  • 当一个线程对象被创建,但还未调用 start 方法时处于新建状态
  • 此时未与操作系统底层线程关联
  • 可运行
  • 调用了 start 方法,就会由新建进入可运行
  • 此时与底层线程关联,由操作系统调度执行
  • 终结
  • 线程内代码已经执行完毕,由可运行进入终结
  • 此时会取消与底层线程关联
  • 阻塞
  • 当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
  • 当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
  • 等待
  • 当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
  • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态
  • 有时限等待
  • 当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
  • 当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
  • 如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
  • 还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

其它情况(只需了解)

  • 可以用 interrupt() 方法打断等待有时限等待的线程,让它们恢复为可运行状态
  • park,unpark 等方法也可以让线程等待和唤醒

1.2 操作系统层次的五种状态

这是从 操作系统 层面来描述的

java await 加时间_java await 加时间_02

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

2. 线程池(七大核心参数)

要求

  • 掌握线程池的 7 大核心参数

七大参数

  1. corePoolSize 核心线程数目 - 池中会保留的最多线程数
  2. maximumPoolSize 最大线程数目 - 核心线程+救急线程的最大数目
  3. keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
  4. unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
  5. workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  6. threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  7. handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略
  1. workQueue满了,救急线程数目也达到最大了,后面来任务直接抛异常 java.util.concurrent.ThreadPoolExecutor.AbortPolicy
  2. java await 加时间_sleep_03

  3. workQueue满了,救急线程数目也达到最大了,新加的任务由调用者执行(谁去submit任务谁就去执行任务) java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy
  4. java await 加时间_wait_04

  5. workQueue满了,救急线程数目也达到最大了,后面来的任务之间丢弃。java.util.concurrent.ThreadPoolExecutor.DiscardPolicy
  6. java await 加时间_java_05

  7. workQueue满了,救急线程数目也达到最大了,直接丢弃最早排队任务 (丢弃排队时间最长的任务,把新的任务添加到workQueue)java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy
  8. java await 加时间_wait_06

示例代码:

java await 加时间_java_07

import org.slf4j.Logger;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static day02.LoggerUtils.*;

public class TestThreadPoolExecutor {

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger c = new AtomicInteger(1);
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                queue,
                r -> new Thread(r, "myThread" + c.getAndIncrement()),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        showState(queue, threadPool);
        threadPool.submit(new MyTask("1", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("2", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("3"));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("4"));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("5", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("6"));
        showState(queue, threadPool);
    }

    private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) {
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        List<Object> tasks = new ArrayList<>();
        for (Runnable runnable : queue) {
            try {
                Field callable = FutureTask.class.getDeclaredField("callable");
                callable.setAccessible(true);
                Object adapter = callable.get(runnable);
                Class<?> clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter");
                Field task = clazz.getDeclaredField("task");
                task.setAccessible(true);
                Object o = task.get(adapter);
                tasks.add(o);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        main.debug("pool size: {}, queue: {}", threadPool.getPoolSize(), tasks);
    }

    static class MyTask implements Runnable {
        private final String name;
        private final long duration;

        public MyTask(String name) {
            this(name, 0);
        }

        public MyTask(String name, long duration) {
            this.name = name;
            this.duration = duration;
        }

        @Override
        public void run() {
            try {
                LoggerUtils.get("myThread").debug("running..." + this);
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "MyTask(" + name + ")";
        }
    }
}

运行结果:

java await 加时间_java_08


3. wait vs sleep

要求

  • 能够说出二者区别

一个共同点,三个不同点

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态。

不同点

  • 方法归属不同
  • sleep(long) 是 Thread 的静态方法
  • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
  • 醒来时机不同
  • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
  • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
  • 它们都可以被打断唤醒(调用线程的 interrupt() 方法)
  • 锁特性不同(重点)
  • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
  • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
  • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

下面是一些示例代码。


3.1 wait() 必须配合锁一起使用

static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        LOCK.wait();
    }

运行结果:

java await 加时间_线程状态_09

static final Object LOCK = new Object();

    public static void main(String[] args) throws InterruptedException {
        synchronized (LOCK){
            LOCK.wait();
        }
    }

java await 加时间_线程状态_10


3.2 wait 方法执行后会释放锁 sleep则不会

示例代码:

java await 加时间_sleep_11


运行结果:

java await 加时间_sleep_12

示例代码:

java await 加时间_java await 加时间_13

运行结果:

java await 加时间_java_14


3.3 interrupt()方法可以提前唤醒sleep线程和wait线程

java await 加时间_线程状态_15


运行结果:

java await 加时间_sleep_16