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

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
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

相关文章
|
缓存 并行计算 算法
MPI并行计算的基本介绍和使用
MPI并行计算的基本介绍和使用
1338 1
|
负载均衡 Dubbo Java
Dubbo 3.x:探索阿里巴巴的开源RPC框架新技术
随着微服务架构的兴起,远程过程调用(RPC)框架成为了关键组件。Dubbo,作为阿里巴巴的开源RPC框架,已经演进到了3.x版本,带来了许多新特性和技术改进。本文将探讨Dubbo 3.x中的一些最新技术,包括服务注册与发现、负载均衡、服务治理等,并通过代码示例展示其使用方式。
890 9
|
Linux 网络安全 开发工具
Git拉取代码的完整示例操作
Git拉取代码的完整示例操作
1570 0
|
存储 XML 安全
Jetpack DataStore 你总要了解一下吧?
一、DataStore 介绍 DataStore 是 Android Jetpack 中的一个组件,它是一个数据存储的解决方案,跟 SharedPreferences 一样,采用key-value形式存储。 DataStore 保证原子性,一致性,隔离性,持久性。尤其是,它解决了 SharedPreferences API 的设计缺陷。 Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences,让应用能够以异步、事务方式存储数据。
1284 0
Jetpack DataStore 你总要了解一下吧?
双T型振荡器主要特点和工作原理介绍
双T振荡器是另一种类型的RC振荡器,它产生正弦波输出,用于类似于电桥桥振荡器的固定频率应用。双T型振荡器在反相放大器的输出和输入之间的反馈回路(因此得名)中使用两个“Tee”形 RC 网络。
2896 0
双T型振荡器主要特点和工作原理介绍
|
8月前
|
缓存 Java Spring
Spring循环依赖:当两个Bean陷入鸡生蛋死循环时...
Spring中循环依赖问题常见于Bean相互依赖时,尤其在单例模式下。文章深入解析了循环依赖的成因及Spring的三级缓存解决方案,帮助理解Bean生命周期与依赖管理。
|
传感器 机器学习/深度学习 算法
【WSN】无线传感器网络模拟器研究Matlab代码实现
【WSN】无线传感器网络模拟器研究Matlab代码实现
【WSN】无线传感器网络模拟器研究Matlab代码实现
|
开发工具 git
project is registered as a Git root, but no Git repositories were found there
报错如下 我一开始想把项目推到git,但是发现右键没有git选项。于是我去搜为什么右键没git选项。给出的答案就是在版本控制中添加。 上图这个我添加的本身就是一个git项目,所以没有出现问题,但是如果本身项目还没有关联远程仓库的话,这样搞是会出现问题。
1185 0
project is registered as a Git root, but no Git repositories were found there
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
网络协议 Python
python对tcp协议栈进行优化之一
**TCP优化摘要:** - MSS优化涉及调整TCP最大段大小,Python中可使用`socket.getsockopt()`查询MSS。 - Scapy是Python库,用于创建和发送网络包,可用于测试和优化协议栈性能。 - LwIP是轻量级TCP/IP协议栈,适合嵌入式设备,可通过分析和调整提升性能,特别是实时性和资源管理。
363 5