JUC学习笔记-线程

3 线程

3.1 线程的创建

  • new Thread对象

    java 复制代码
    Thread t = new Thread() {
        public void run() {
            // task
        }
    };
    // start thread
    t.setName("t1");
    t.start();
  • 实现Runnable接口

    java 复制代码
    Runnable threadTask = new Runnable() {
        public void run() {
            // task
        }
    }
    
    Thread t = new Thread(threadTask);
    
    t.start();
  • 实现Callable接口

    java 复制代码
    // 1. 实现Callable接口,重写call方法
    public myCallable = new Callable<V> {}
    
    // 2. 创建FutureTask对象
    myCallable callable = new myCallable();
    FutureTask<V> futureTask = new FutureTask<>(callable);
    
    // 3. ThreadPoolExecutor执行
    threadPoolExecutor.excute(futureTask);
  • 线程池

3.2 线程应用

3.2.1 线程运行查看

  • windows
    • 任务管理器
    • tasklist [| findstr str] 查看进程
    • taskkill /F /PID pid 杀死进程
  • linux
    • ps -fe 查看所有进程
    • ps -fe | grep str
    • ps -fT -p <PID> 查看某个进程的所有线程
    • kill 杀死进程
    • top 按大写H切换是否显示线程
    • top -H -p <PID> 查看某个进程的所有线程
  • Java
    • jps 查看所有Java进程
    • jstack <PID> 查看某个Java进程的所有线程状态
    • jconsole 通过图形化界面查看某个Java进程中线程的运行情况

3.2.2 线程重要API

startrun

  • start:启动一个新线程,在新线程中运行run方法中的代码。执行start后线程状态由NEW变为RUNNABLE,线程就绪,代码不一定立刻执行(cpu时间片未分配)。
  • run:新线程启动后调用的方法。
  • 线程对象调用start方法是创建的线程执行,调用run方法是主线程执行。
  • 多次调用start 异常IllegalThreadStateException

sleep

  • 调用sleep方法,线程状态 running -> timed waiting
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,sleep方法会抛出InterruptedException异常。
  • 睡眠结束的线程不一定立刻得到执行。

yield

  • 调用yield方法,会让当前线程状态 running -> runnable,然后调度执行其他线程(具体实现依赖操作系统的任务调度器,没有其他就绪状态线程,依然运行当前线程)。

join

调用join等待当前线程结束,join(long n)等待当前线程 最多等待n毫秒。

  • 同步:需要等待结果返回 再继续运行。
  • 异步:不需要等待结果返回 就继续运行。

interrupt

  • 打断阻塞线程(sleep、wait、join),清空打断状态(isInterruptedfalse)。
  • 打断正常运行的线程,清空打断状态。

两阶段终止,怎么在一个线程中终止另一个线程?

  • 使用stop杀死线程不可行,如果被杀死线程持有锁,就无法释放,其他线程也无法获取这个锁。
  • 使用interrupt两阶段打断:
    • 结束线程方法中调用interrupt
    • 启动线程方法中,通过currentThread获取当前线程,判断当前线程的isInterrupted打断状态,如果被打断了 进行锁释放等处理 之后结束;如果没有打断 但抛出异常 catch中重新设置打断状态。

守护线程

  • 默认情况下,Java进程需要等待所有线程都运行结束,才会结束。
  • 守护线程setDaemon,只要其他非守护线程都运行结束,即使守护线程代码没有执行完 也会强制结束。垃圾回收器线程、Tomcat的Acceptor、Poller线程都是守护线程。

3.2.3 线程状态

操作系统层面:

  • 初始:创建了线程对象,还没有和操作系统线程关联。
  • 可运行:就绪状态,线程已经创建完成,得到cpu分配的时间片就可以被调度执行。
  • 运行:获取了cpu时间片调度运行,cpu时间片用完》线程的上下文切换》运行状态变为就绪状态。
  • 阻塞:等待某一事件导致线程上下文切换 从运行状态》阻塞状态(e.g. I/O操作等)。阻塞状态线程只要不唤醒,调度就不会考虑调度它。
  • 终止:线程执行完成,声明周期结束。

Java Thread.State层面:

  • NEW: 线程刚被创建,没有启动。
  • RUNNABLE: 涵盖操作系统层面的就绪、运行、阻塞状态。在Java API层面,调用了start方法等待cpu调度。
  • BLOCKED: 线程执行中没有获取到锁对象。
  • WAITING: 无限等待,需要其他线程notify唤醒。
  • TIMED WAITING: 计时等待。
  • TEMINATED: run方法正常退出 / 没有捕获的异常终止了run方法。

3.3 原理

3.3.1 Thread创建源码

  • Runnable

    实现Runnable接口得到threadTask对象,作为参数传入Thread创建线程对象。

    实际是在Thread重载init方法中将task赋为threadTask,然后执行run方法启动线程。

  • Callable

    Callable相较于Runnbale有返回值也能抛出异常。

    FutureTask实现RunnableFuture接口 -> 实现RunnableFuture接口,Future接口来返回线程执行结果。

3.3.2 线程运行原理

栈与栈帧

每个线程启动后,Java虚拟机栈就会为其分配一块栈内存。

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

线程上下文切换

导致cpu不执行当前线程,执行其他线程的原因:

  • 线程的cpu时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了sleepyieldwaitjoinparksynchronizedlock等方法。

线程上下文切换时,需要操作系统保存当前线程的状态,并恢复另一个线程的状态。

  • 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息(局部变量、操作数栈、返回地址等)。
  • Java程序计数器,负责记住下一条JVM指令的执行地址,是线程私有的。
相关推荐
鬼火儿13 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin13 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧14 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧14 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧14 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧14 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧14 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧15 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧15 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang15 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构