Java 并发 -- 线程状态(Java线程六种状态操作系统层次的五种状态)线程池(七大核心参数)wait vs sleep

Posted CodeJiao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 并发 -- 线程状态(Java线程六种状态操作系统层次的五种状态)线程池(七大核心参数)wait vs sleep相关的知识,希望对你有一定的参考价值。

文章目录


1. 线程状态

要求

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

1.1 Java线程六种状态及转换


分别是

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

其它情况(只需了解)

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

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

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

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联

  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 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. workQueue满了,救急线程数目也达到最大了,新加的任务由调用者执行(谁去submit任务谁就去执行任务) java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy

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

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

示例代码:

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 + ")";
        
    

运行结果:


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();
    

运行结果:

    static final Object LOCK = new Object();

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


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

示例代码:

运行结果:

示例代码:

运行结果:


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


运行结果:



以上是关于Java 并发 -- 线程状态(Java线程六种状态操作系统层次的五种状态)线程池(七大核心参数)wait vs sleep的主要内容,如果未能解决你的问题,请参考以下文章

Java面试题-面试题并发篇

Java面试题-面试题并发篇

大厂高频面试:Java并发篇(线程6种状态线程池悲观锁vs乐观锁等)

JUC并发编程 -- 线程的五种状态( 操作系统层面) 和 六种状态(Java API层面)

Java并发编程的艺术读书笔记——Java并发编程基础

并发编程专题-线程的状态