V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MeloForsaken
V2EX  ›  程序员

菜鸟提问,有关 Threadlocal 的问题

  •  
  •   MeloForsaken · 71 天前 · 1464 次点击
    这是一个创建于 71 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1.假设默认核心线程是 10 ,Threadlocal 使用 Thread.currentThread()来定位当前线程,但是如果客户端并发请求数量超过核心线程,那么线程必定是被复用的,那也就是说 Thread.currentThread()肯定会重复,那为什么 ThreadLocal 还能保证线程安全(源码没看懂)? 由此引申出来其他几个问题: 2.如果客户端并发请求超过核心线程(核心线程是 10),是不是超出的请求都阻塞了(实际使用应该没有阻塞,不知道线程是怎么执行的)?还是轮着执行各个请求吗? 3.接着上一个问题,springboot 内置的 tomcat 线程池和 springboot 的线程池有什区别吗? 4.现在更加懵逼了,一个 web 请求过来线程到底是怎么处理的? 5.有否相关的书籍可以查看?

    谢谢!

    18 条回复    2022-09-29 13:11:07 +08:00
    fkdog
        1
    fkdog  
       71 天前
    线程这玩意不是说你 excute 了一个 runnable 以后就开一个线程,而是把 runnable 任务放到一个队列里,然后有 N 个 worker 工作线程从里边取 runnable 来执行。

    你一次提交超过 10 个任务,多的任务会放在对列里继续等 worker 取用。
    hidemyself
        2
    hidemyself  
       71 天前
    线程复用,复用的是 worker ,工作线程,和你这段代码所处的地方不是一个东西
    wolfie
        3
    wolfie  
       71 天前
    while true worker.run(Runnable)
    currentThread 会获取到 worker 的线程。
    7911364440
        5
    7911364440  
       71 天前
    1. 线程复用跟线程安全没关系吧,ThreadLocal 中的数据还是只有当前线程可以访问啊
    2. 超出的请求会进入等待队列
    3. 程序中自定义的线程池和 tomcat 线程池没关系
    4. 容器收到请求后会根据 url 执行对应的代码,代码是由 tomcat 线程池中的工作线程来执行的。

    我也不知道说的对不对😅
    ediron
        6
    ediron  
       71 天前
    所以 ThreadLocal 使用完后要手动删除
    timethinker
        7
    timethinker  
       71 天前
    1 、如果你没有及时清理 ThreadLocal 中存放的变量,线程实例也没有销毁,那么当这个线程实例执行下一个任务时,如果任务中有访问到同一个 ThreadLocal 存放的变量,就会造成一些状态依赖问题,可能会破坏依赖这些状态的代码逻辑。

    2 、一般线程池有一个缓存队列用来缓存任务,当有可用的线程时,就会取出队列中的请求交给线程执行,取决于线程池的相关设置和策略,这部分在创建线程池的时候都是可以设置的。相关介绍: https://www.baeldung.com/thread-pool-java-and-guava

    3 、同上线程池可以是同一个实例(一组线程),也可以是不同的实例(多组线程),取决于配置。

    4 、这个跟使用语言的框架有关,比如 Java 通常使用 Spring MVC 或者 WebFlux 等等,现在的框架数不胜数,不过我建议你最好看一下 HTTP 协议相关的内容。

    5 、如果是 HTTP 协议相关的,《 HTTP 权威指南》值得一读,其实不一定要买书,很多在线的网站资源都能够系统的学到一些知识,虽然比较零散。
    AS4694lAS4808
        8
    AS4694lAS4808  
       71 天前
    1.ThreadLocal 在线程复用的时候值应该是上一次留下的吧,所以要有初始化或者清除值的逻辑
    2.进入队列或者抛出异常
    3.谁都可以定义线程池,两个库之间的没啥关系
    后两个问题百度下文章一大堆。。
    issakchill
        9
    issakchill  
       71 天前
    使用后请务必 remove
    YepTen
        10
    YepTen  
       71 天前   ❤️ 2
    真的是个菜鸟啊,和之前的我一样。
    1. 你先看下线程池的基本使用和概念,网上有很多讲解线程池的文章。推荐微信公众号:低并发编程。
    2. 你第 3 个问题,tomcat 线程池和 springboot 的线程池(这个我理解我 springboot 内置的异步线程池)是完全不同的 2 个东西,但他们都是线程池。举个例子:他们都是“狗”这个东西,但一个是二哈,一个是泰迪,内在都一样。PS:没有 springboot 内置 tomcat 线程池这一说法,应该是 springboot 内置了 tomcat 容器( Spring Boot 支持 Tomcat 、Jetty 和 Undertow 三种 Servlet 容器,只不过 Tomcat 是默认的那种,而 tomcat 有自己的线程池)。
    3. 你第 4 个问题通读 tomcat 源码可以给你答案。简单描述就是:一个 web 请求并不是自始至终只有 1 个线程去处理,从接受请求到返回响应,有 N 多个不同的线程去处理,这些线程又可能属于不同的线程池。首先由 tomcat 去接收这个请求,接收后,交给你的 service 去处理(这必然又是一个新线程了),处理完了在交给 tomcat 去返回给调用方。
    4. 如果想理清 web 请求,推荐先看下 http 协议,书推荐:图解 HTTP ,HTTP 权威指南。
    对于 tomcat ,在你没理解 http 协议前,我个人不建议你看讲解 tomcat 的书。
    我说下我的看法,仅供参考。
    Tomcat 结构总体分为 2 部分,连接器和 servlet 容器。你 查看 Tomcat 的介绍时,一般都会看到“Tomcat 是由 Apache 软件基金会属下 Jakarta 项目开发的 Servlet 容器”这样的表述。都会告诉你,他是个 servlet 容器,对连接器的侧重少些。那连接器是什么,有啥用?答案是:连接器实现了 HTTP 协议,负责网络链接(就是 socket 那一套)。网络上数据都是二进制形式传输的(就是 1000111111 ,这种二进制数据),连接器接受这些二进制数据后,将他们解析成 HttpServletRequest 这个 Java 对象(这个过程很有意思,Netty 也是干网络链接的,都是怎么把二进制解析为 java 对象),然后将这个对象交给 servlet 容器。我们平常开发写的 Controller 层,service 层,都在 servlet 容器中。
    连接器他只是接收数据,并不处理,你搞明白后,自然就会想,他只接收,那谁处理呢?然后再去看 servlet 容器,水到渠成的事情。
    你看 servlet 协议,会发现他没有规定如何与前端通信,起点就是我有个 Request 对象,怎么处理。
    对于通信这个,tomcat 连接器是有,但相比 netty 和 ngnix 相比,性能没他们好。一般部署时,都是把 tomcat 部署在内网,集群形式,前面部署个 ngnix 去接收互联网的流量,然后由 ngnix 转发到 tomcat ,这种是你侧重 tomcat 只干 servlet 容器的活,ngnix 相当于他的连接器。你如果直接部署 tomcat 对接互联网,他默认的是 200 并发,相当于他又干连接器又干 servlet 容器,结果啥都干不好。
    cyningxu
        11
    cyningxu  
       71 天前
    这。。。一时间竟不知道该说啥,还是先把基础搞清楚吧
    Jooooooooo
        12
    Jooooooooo  
       71 天前
    进来的时候塞值, 出去的时候把值清空.
    MeloForsaken
        13
    MeloForsaken  
    OP
       70 天前
    感谢大家,有点体会到了
    nothingistrue
        14
    nothingistrue  
       70 天前
    线程安全,跟并发请求、异步线程池、NIO ,那是出十服的关系。

    一个数据,不能被多个线程同时修改,这就是线程安全的,Threadlocal 的数据只能被固定的线程访问,它自然是线程安全的。
    812603834
        15
    812603834  
       70 天前
    可以看下 @YepTen 大佬的回复。
    你的第四个问题,大佬用第三点答复了,你的问题可以看成 2 种问题
    1.一个外网请求,从请求到响应是怎么支持的?
    2.结合你的 threadLocal 这个问题,问的应该是一个服务内请求是怎么执行的?
    大佬的回复是回复第一个问题的,一个外网请求从接受到响应可能会经历 nginx 、网关、路由、注册中心、再到服务,这每个中间件都有线程去处理他。
    第二个问题自始至终这个请求都是一个线程在执行,所以这个线程执行完之后,必须要清除 threadLocal,不然放回线程池,再被使用时,就造成数据混乱
    cubecube
        16
    cubecube  
       70 天前
    Thread 对象不是复用的呀
    ming159
        17
    ming159  
       70 天前
    1. 一个 CPU 核心,在一个具体的时间点,注意是时间点,比如 某 xxx 纳秒的时候,是仅仅有一个线程是运行状态,而其它 9 个都不是. 建议复习一下 线程的生命周期. 另外,线程是你代码执行的容器,并不是你代码本身. 当你代码在某个线程中执行完后,线程就销毁了. 而线程池则是让你代码执行完后,线程并不销毁,而是重复使用. 再就是线程安全主要指的是内存中变量的读写在同一时刻只有一个线程可以操作.
    2. 并发数超过线程数后,请求确实是排队中的. 只不过每个请求的处理时间很短,基本毫秒级别就结束了. 打个比方,某个请求处理耗时 5ms,那 1 个线程 1 秒就可以处理 200 个请求.理论上 10 个线程就是 2000 个请求,但线程切换是也是要消耗时间的.所以实际上达不到 2000,也正是因为线程切换要消耗资源和时间所以线程数不是越大越好.
    RedBeanIce
        18
    RedBeanIce  
       70 天前
    先说一下线程池,我理解的线程池里面就是一堆 thread

    --------------------------

    那为什么 ThreadLocal 还能保证线程安全(源码没看懂),他可不保证线程安全哦,ThreadLocal 可以理解为是一个 map ,key 是 thread ,value 是存在里面的值,因为线程池的情况下 thead 是复用的,所以每个线程执行完任务,记得清理本线程的 ThreadLocal

    ----------------------------

    .如果客户端并发请求超过核心线程(核心线程是 10),是不是超出的请求都阻塞了(实际使用应该没有阻塞,不知道线程是怎么执行的)?
    回复:请看一下线程池的源码,有基础线程池线程数,最大线程池线程数,阻塞队列

    ---------------------------------------------------------

    接着上一个问题,springboot 内置的 tomcat 线程池和 springboot 的线程池有什区别吗?,对于 Java 来说,请看线程池源码

    -------------------------------------------------------------
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1547 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 41ms · UTC 23:43 · PVG 07:43 · LAX 15:43 · JFK 18:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.