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指令的执行地址,是线程私有的。
相关推荐
淬渊阁5 小时前
Hello world program of Go
开发语言·后端·golang
Pandaconda5 小时前
【新人系列】Golang 入门(十五):类型断言
开发语言·后端·面试·golang·go·断言·类型
周Echo周5 小时前
16、堆基础知识点和priority_queue的模拟实现
java·linux·c语言·开发语言·c++·后端·算法
魔道不误砍柴功6 小时前
Spring Boot自动配置原理深度解析:从条件注解到spring.factories
spring boot·后端·spring
风象南7 小时前
基于Redis的3种分布式ID生成策略
redis·后端
魔道不误砍柴功7 小时前
Spring Boot 核心注解全解:@SpringBootApplication背后的三剑客
java·spring boot·后端
Asthenia04128 小时前
分布式唯一ID实现方案详解:数据库自增主键/uuid/雪花算法/号段模式
后端
Asthenia04128 小时前
内部类、外部类与静态内部类的区别详解
后端
Asthenia04128 小时前
类加载流程之初始化:静态代码块的深入拷打
后端