• Java中的多线程很难?【扫盲研报】


    前言】Java中的多线程确实很难,我认为是JavaWeb开发过程中最难的一个知识点,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等。话不多说,我们直接上干货,篇幅有点长,小伙伴们坐稳了~

    进程与线程

    进程:

    • 当一个程序被运行,就开启了一个进程, 比如启动了qq,word
    • 程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备

    线程:

    • 一个进程内可分为多个线程
    • 一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令

    多线程:

    •  指的是这个程序(一个进程)运行时产生了不止一个线程

    并行与并发

    并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

    并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。

     

     为什么需要使用多线程?

    多线程能实现的都可以用单线程来完成,那单线程运行的好好的,为什么java要引入多线程的概念呢?

    多线程的好处:

    1. 程序运行的更快!更快~就意味着可以压榨计算机的性能!
    2. 充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力!

     为什么很多人都觉得多线程很难,那么它难在哪里呢?

    个人认为:单线程只有一条执行线,过程容易理解,可以在大脑中清晰的勾勒出代码的执行流程

    多线程却是多条线,而且一般多条线之间有交互(多条线之间需要通信),所以总结了以下几个点:

    1:  多线程的安全问题

    2:线程资源宝贵,依赖线程池操作线程,线程池的参数设置问题

    3:多线程执行是动态的,同时的,难以追踪过程

    java多线程的基本使用定义任务、创建和运行线程

    任务:线程的执行体。也就是我们的核心代码逻辑

    定义任务

    1. 继承Thread类 (可以说是 将任务和线程合并在一起)
    2. 实现Runnable接口 (可以说是 将任务和线程分开了)
    3. 实现Callable接口 (利用FutureTask执行任务)

    Thread实现任务的局限性

    1. 任务逻辑写在Thread类的run方法中,有单继承的局限性
    2. 创建多线程时,每个任务有成员变量时不共享,必须加static才能做到共享

    Runnable和Callable解决了Thread的局限性,但是Runbale相比Callable有以下的局限性

    1. 任务没有返回值
    2. 任务无法抛异常给调用方

     几种定义线程的方式,案例如下:

    1. class T extends Thread {
    2. @Override
    3. public void run() {
    4. log.info("继承Thread的任务");
    5. }
    6. }
    7. class R implements Runnable {
    8. @Override
    9. public void run() {
    10. log.info("实现Runnable的任务");
    11. }
    12. }
    13. class C implements Callable {
    14. @Override
    15. public String call() throws Exception {
    16. log.info("实现Callable的任务");
    17. return "成功";
    18. }
    19. }

    创建线程的方式

    1. 通过Thread类直接创建线程
    2. 利用线程池内部创建线程

    启动线程的方式

    • 调用线程的start()方法
    1. // 启动继承Thread类的任务
    2. new T().start();
    3. // 启动继承Thread匿名内部类的任务 可用lambda优化
    4. Thread t = new Thread(){
    5. @Override
    6. public void run() {
    7. log.info("我是Thread匿名内部类的任务");
    8. }
    9. };
    10. // 启动实现Runnable接口的任务
    11. new Thread(new R()).start();
    12. // 启动实现Runnable匿名实现类的任务
    13. new Thread(new Runnable() {
    14. @Override
    15. public void run() {
    16. log.info("我是Runnable匿名内部类的任务");
    17. }
    18. }).start();
    19. // 启动实现Runnable的lambda简化后的任务
    20. new Thread(() -> log.info("我是Runnable的lambda简化后的任务")).start();
    21. // 启动实现了Callable接口的任务 结合FutureTask 可以获取线程执行的结果
    22. FutureTask target = new FutureTask<>(new C());
    23. new Thread(target).start();
    24. log.info(target.get());

    上下文切换

    多核cpu下,多线程是并行工作的,如果线程数多,单个核又会并发的调度线程,运行时会有上下文切换的概念

    cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。

    1. 线程的cpu时间片用完
    2. 垃圾回收
    3. 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

    当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。

    线程的礼让-yield()&线程的优先级

    yield()方法会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配。

    代码如下

    1. // 方法的定义
    2. public static native void yield();
    3. Runnable r1 = () -> {
    4. int count = 0;
    5. for (;;){
    6. log.info("---- 1>" + count++);
    7. }
    8. };
    9. Runnable r2 = () -> {
    10. int count = 0;
    11. for (;;){
    12. Thread.yield();
    13. log.info(" ---- 2>" + count++);
    14. }
    15. };
    16. Thread t1 = new Thread(r1,"t1");
    17. Thread t2 = new Thread(r2,"t2");
    18. t1.start();
    19. t2.start();

    万字图解Java多线程,不信你学不会! - 知乎

    【ThreadPoolTaskExecutor】 SpringBoot 的线程池的使用__Peko_的博客-CSDN博客_threadpooltaskexecutor线程池使用

    ThreadPoolTaskExecutor_小甄笔记的博客-CSDN博客_threadpooltaskexecutor

  • 相关阅读:
    SpringMVC之JSON数据返回与异常处理机制---全方面讲解
    Linux学习-69-Linux系统启动管理
    【Java中23种面试常考的设计模式之单例模式(Singleton)---创建型模式】
    软件杯 深度学习YOLO图像视频足球和人体检测 - python opencv
    [PTP][1588v2] 报文通用格式
    【GNN报告】ICT敖翔:图机器学习应对金融欺诈对抗攻击
    信钰证券:碧桂园大涨,石墨烯拉升
    进程间通信IPC-管道
    luckysheet 国产超强纯前端在线excel表格功能强大 简单使用记录 异常报错记录及处理
    你听说过LabVIEW吗?
  • 原文地址:https://blog.csdn.net/qq_43143025/article/details/126482896