深入简出的带你精通java线程

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS AI 助手,专业版
简介: 线程与进程是操作系统中的两个重要概念。进程是资源分配的最小单位,负责加载指令、管理内存和IO;线程是CPU调度的最小单位,也被称为轻量级进程。

线程与进程

  • 进程:用来加载指令、管理内存、管理IO。操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是资源分配的最小单位。
  • 线程:有时被称为轻量级进程(Lightweight Process,LWP),是操作系统调度(CPU调度)执行的最小单位。

进程间通信的方式

  • 管道(pipe)及有名管道(named pipe) :管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
  • 信号(signal) :信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。
  • 消息队列(message queue) :消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新信息;对消息队列有读权限的进程则可以从消息队列中读取信息。
  • 共享内存(shared memory) :可以说这是最有用的进程间通信方式。它使的多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
  • 信号量(semaphore) :主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。
  • 套接字(socket) :这是一种更为一般的进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

线程的同步互斥

  • 线程同步:线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
  • 线程互斥:对于共享的进程系统资源,在各单个线程访问时的排它性。
线程同步互斥的控制方法
  • 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。(在一段时间内只允许一个线程访问的资源就称为临界资源)。
  • 互斥量:为协调共同对一个共享资源的单独访问而设计的。
  • 信号量:为控制一个具有有限数量用户资源而设计。
  • 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

线程上下文切换(Context switch):一般要5-10毫秒

  • 下文切换是指CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换。
  • 是多任务操作系统的一个基本特性。
  • 切换的耗时一般为5-10毫秒。
  • 上下文切换只能在内核模式下发生!

操作系统层面线程状态:初始状态、可运行状态、运行状态、休眠状态和终止状态。

  • 初始状态:指的是线程已经被创建,但是还不允许分配 CPU 执行。
  • 可运行(就绪)状态:指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
  • 运行状态:获取到CPU的时间片。
  • 休眠状态:运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。
  • 终止状态:线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。

java的线程状态

  • 取自Thread类的内部枚举

java

代码解读

复制代码

    public enum State {
        // 初始化状态
        NEW,

        // 可运行状态+运行状态
        RUNNABLE,

        // 阻塞状态
        BLOCKED,

        // 无时限等待
        WAITING,

        // 有时限等待
        TIMED_WAITING,

        // 终止状态
        TERMINATED;
    }

面试中问你线程的状态,你应该如何回答?

  • 在操作系统层面有5种,java中有6种。
  • Java线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态,即操作系统的休眠状态。这三种状态永远没有CPU的使用权!
  • Java线程中的 RUNNABLE 状态,在操作系统中分为:可运行(就绪)状态、运行状态。

Java线程的实现方式(4种)

  • 使用 Thread类或继承Thread类

java

代码解读

复制代码

// 创建线程对象
Thread t = new Thread() {
    public void run() {
    // 要执行的任务
    }
};
// 启动线程
  • 实现 Runnable 接口配合Thread:线程(Thread)与任务(Runnable)分开

java

代码解读

复制代码

Runnable runnable = new Runnable() {
    public void run(){
    // 要执行的任务
    }
};
// 创建线程对象
Thread t = new Thread( runnable );
// 启动线程
  • 使用有返回值的 Callable

java

代码解读

复制代码

class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
  • 使用 lambda

java

代码解读

复制代码

new Thread(() -> System.out.println(Thread.currentThread().getName())).start();

Thread常用方法

sleep方法
  • 调用 sleep 会让当前线程从 Running 进入TIMED_WAITING状态,不会释放对象锁
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException,并且会清除中断标志
  • 睡眠结束后的线程未必会立刻得到执行
  • sleep当传入参数为0时,和yield相同
yield方法
  • yield会释放CPU资源,让当前线程从 Running 进入 Runnable状态,让优先级更高(至少是相同)的线程获得执行机会,不会释放对象锁;
  • 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程;
  • 具体的实现依赖于操作系统的任务调度器
join方法
  • 等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。

如何正确优雅的停止线程?

stop方法
  • stop()方法已经被jdk废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止。
Java线程的中断机制
  • 中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。
  • 被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。
  • interrupt(): 将线程的中断标志位设置为true,不会停止线程
  • isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位
  • Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle
  • sleep可以被中断 抛出中断异常:sleep interrupted, 清除中断标志位
  • wait可以被中断 抛出中断异常:InterruptedException, 清除中断标志位

Java线程间通信方式

  • volatile:两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
  • 等待唤醒(等待通知)机制:基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,线程将进入等待队列进行等待直到被唤醒。
  • LockSupport:JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用unpark则为指定线程提供“许可”。
  • 管道输入输出流:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
  • Thread.join:可以简单理解为线程合并。一个线程等待到另一个线程执行完。

为什么说本质上Java中实现线程只有一种方式?

java

代码解读

复制代码


    private Runnable target;

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
  • 继承Thread类,其实就是使用的Runnable对象调用run方法

java

代码解读

复制代码

public interface ThreadFactory {

    Thread newThread(Runnable r);
}
  • 线程池的创建,本质上也是传入一个Runnable对象
  • 使用lambda的方式,本质与继承的方式一致
  • 所以,创建线程的本质只有一种!

简单了解start()方法是如何开启一个线程的(JVM与操作系统层面)?

  • 使用new Thread()创建一个线程,然后调用start()方法进行java层面的线程启动;
  • 调用本地方法start0(),去调用jvm中的JVM_StartThread方法进行线程创建和启动;
  • 调用new JavaThread(&thread_entry, sz)进行线程的创建,并根据不同的操作系统平台调用对应的os::create_thread方法进行线程创建;
  • 新创建的线程状态为Initialized,调用了sync->wait()的方法进行等待,等到被唤醒才继续执行thread->run();
  • 调用Thread::start(native_thread);方法进行线程启动,此时将线程状态设置为RUNNABLE,接着调用os::start_thread(thread),根据不同的操作系统选择不同的线程启动方式;
  • 线程启动之后状态设置为RUNNABLE, 并唤醒第4步中等待的线程,接着执行thread->run()的方法;
  • JavaThread::run()方法会回调第1步new Thread中复写的run()方法。


转载来源:https://juejin.cn/post/7067440659828850725

相关文章
|
12月前
|
IDE Java 应用服务中间件
spring boot 启动流程
Spring Boot 启动流程简介: 在使用 Spring Boot 之前,启动 Java Web 应用需要配置 Web 容器(如 Tomcat),并将应用打包放入容器目录。而使用 Spring Boot,只需运行 main() 方法即可启动 Web 应用。Spring Boot 的核心启动方法是 SpringApplication.run(),它负责初始化和启动应用上下文。 主要步骤包括: 1. **应用启动计时**:使用 StopWatch 记录启动时间。 2. **打印 Banner**:显示 Spring Boot 的 LOGO。 3. **创建上下文实例**:通过反射创建
704 5
|
9月前
|
人工智能 监控 安全
Go通道机制与应用详解
本文全面解析了Go语言中的通道(Channel),从基础概念到高级应用,涵盖创建、操作、垃圾回收及实际场景使用。通道作为Go并发模型的核心,支持协程间安全高效的数据通信与同步。文章介绍了无缓冲和有缓冲通道的特性,以及发送、接收、关闭等操作,并探讨了`select`语句、超时处理、遍历通道等高级用法。此外,还深入分析了通道的垃圾回收机制,包括引用计数、生命周期管理和循环引用问题。最后通过数据流处理、任务调度和状态监控等实例,展示了通道在实际开发中的广泛应用。理解通道不仅有助于构建高并发系统,还能优化资源管理,提升程序性能。
318 31
|
8月前
|
SQL 缓存 关系型数据库
MySQL 慢查询是怎样优化的
本文深入解析了MySQL查询速度变慢的原因及优化策略,涵盖查询缓存、执行流程、SQL优化、执行计划分析(如EXPLAIN)、查询状态查看等内容,帮助开发者快速定位并解决慢查询问题。
336 0
|
10月前
|
安全 Java 程序员
巧用Optional之优雅规避NPE问题
本文探讨了Java中常见的NullPointerException问题及其解决方案,重点介绍了Optional类的使用。通过实例代码分析,展示了如何用Optional替代传统的空值检查,使代码更简洁、优雅。文章详细讲解了Optional的创建方法(如of、ofNullable、empty)及常用方法(如get、orElse、map、flatMap、filter),并通过实战案例演示了其在实际开发中的应用,帮助开发者有效避免NPE问题,提升代码质量。
244 2
巧用Optional之优雅规避NPE问题
|
12月前
|
消息中间件 监控 RocketMQ
Docker部署RocketMQ5.2.0集群
本文详细介绍了如何使用Docker和Docker Compose部署RocketMQ 5.2.0集群。通过创建配置文件、启动集群和验证容器状态,您可以快速搭建起一个RocketMQ集群环境。希望本文能够帮助您更好地理解和应用RocketMQ,提高消息中间件的部署和管理效率。
1637 91
|
12月前
|
Java API Spring
Java小抄 使用StopWatch输出执行耗时
通过本文的介绍,我们详细讲解了如何使用 `StopWatch` 类测量代码执行时间。`StopWatch` 提供了简单而强大的功能,帮助我们精确分析代码的性能瓶颈,优化程序效率。希望本文能帮助您更好地理解和应用 `StopWatch`,在实际开发中提高代码性能和质量。
2097 80
|
7月前
|
SQL 人工智能 关系型数据库
如何使用MySQL的事件调度器?
MySQL事件调度器允许在指定时间或间隔自动执行SQL语句,可用于数据清理、报告生成等任务。本文介绍其配置、创建、修改、删除事件的方法,并提供Java操作示例代码,帮助实现数据库定时任务管理。
319 0
|
9月前
|
人工智能 安全 Java
Java并发包下Atomic相关类的使用
本文介绍了 `java.util.concurrent.atomic` 包下的各类原子类及其使用场景,包括基本类型原子类(如 `AtomicInteger`、`AtomicLong`)、数组类型原子类(如 `AtomicIntegerArray`)、引用类型原子类(如 `AtomicReference`)、对象属性修改原子类(如 `AtomicIntegerFieldUpdater`)以及原子操作增强类(如 `LongAdder` 和 `LongAccumulator`)。同时,详细对比了不同原子类在高并发场景下的性能表现,展示了 `LongAdder` 的高效性。
226 31
|
9月前
|
人工智能 Java
Java参数传递分析
本文详细探讨了Java中参数传递的机制,明确指出Java采用的是值传递而非引用传递。通过基本数据类型(如int)和引用类型(如Map、自定义对象People)的实例测试,证明方法内部对参数的修改不会影响原始变量。即使在涉及赋值返回的操作中,表面上看似引用传递,实际仍是值传递的结果。文中结合代码示例与执行结果,深入解析了值传递的本质及容易引起混淆的情形,帮助读者准确理解Java参数传递的核心概念。
186 7
|
10月前
|
SQL 存储 关系型数据库
MySQL选错索引了怎么办?
本文探讨了MySQL中因索引选择不当导致查询性能下降的问题。通过创建包含10万行数据的表并插入数据,分析了一条简单SQL语句在不同场景下的执行情况。实验表明,当数据频繁更新时,MySQL可能因统计信息不准确而选错索引,导致全表扫描。文章深入解析了优化器判断扫描行数的机制,指出基数统计误差是主要原因,并提供了通过`analyze table`重新统计索引信息的解决方法。
283 3