1.通过transmittable-thread-local源码理解线程池线程本地变量传递的源码原理
2.UE4源码剖析——异步与并行 中篇 之 Thread
3.ãPocoç¬è®°ã线ç¨Thread
4.硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理
5.Qt——QThread源码浅析
6.InheritableThreadLocal源码剖析
通过transmittable-thread-local源码理解线程池线程本地变量传递的原理
最近几周,我投入了大量的解析时间和精力,完成了UCloud服务和中间件迁移至阿里云的源码工作,因此没有空闲时间撰写文章。解析不过,源码回忆起很早之前对ThreadLocal源码的解析黑客黑网页源码分析,其中提到了ThreadLocal存在向预先创建的源码线程中传递变量的局限性。恰好,解析我的源码一位前同事,HSBC的解析技术大牛,提到了团队引入了transmittable-thread-local(TTL)来解决此问题。源码借此机会,解析我深入分析了TTL源码,源码本文将全面分析ThreadLocal和InheritableThreadLocal的解析局限性,并深入探讨TTL整套框架的源码实现。如有对线程池和ThreadLocal不熟悉的读者,建议先阅读相关前置文章,本篇文章行文较为干硬,字数接近5万字,希望读者耐心阅读。
在Java中,没有直接的API允许子线程获取父线程的实例。获取父线程实例通常需要通过静态本地方法Thread#currentThread()。同样,为了在子线程中传递共享变量,也常采用类似的方法。然而,这种方式会导致硬编码问题,限制了方法的复用性和灵活性。为了解决这一问题,线程本地变量Thread Local应运而生,其基本原理是通过线程实例访问ThreadLocal.ThreadLocalMap来实现变量的存储与传递。
ThreadLocal与InheritableThreadLocal之间的区别主要在于控制ThreadLocal.ThreadLocalMap的创建时机和线程实例中对应的属性获取方式。通过分析源码,可以清楚地看到它们之间的联系与区别。对于不熟悉概念的读者,可以尝试通过自定义实现来理解其中的原理与关系。
ThreadLocal和InheritableThreadLocal的最大局限性在于无法为预先创建的线程实例传递变量。泛线程池Executor体系、TimerTask和ForkJoinPool等通常会预先创建线程,因此无法在这些场景中使用ThreadLocal和InheritableThreadLocal来传递变量。数大小大于127的源码
TTL提供了更灵活的解决方案,它通过委托机制(代理模式)实现了变量的传递。委托可以基于Micrometer统计任务执行时间并上报至Prometheus,然后通过Grafana进行监控展示。此外,TTL通过字节码增强技术(使用ASM或Javassist等工具)实现了类加载时期替换Runnable、Callable等接口的实现,从而实现了无感知的增强功能。TTL还使用了模板方法模式来实现核心逻辑。
TTL框架的核心类TransmittableThreadLocal继承自InheritableThreadLocal,通过全局静态变量holder来管理所有TransmittableThreadLocal实例。holder实际上是一个InheritableThreadLocal,用于存储所有线程本地变量的映射,实现变量的全局共享。disableIgnoreNullValueSemantics属性的设置可以影响NULL值的处理方式,影响TTL实例的行为。
发射器Transmitter是TransmittableThreadLocal的一个公有静态类,提供传输TransmittableThreadLocal实例和注册当前线程变量至其他线程的功能。通过Transmitter的静态方法,可以实现捕获、重放和复原线程本地变量的功能。
TTL通过TtlRunnable类实现了任务的封装,确保在执行任务时能够捕获和传递线程本地变量。在任务执行前后,通过capture和restore方法捕获和重放变量,实现异步执行时上下文的传递。
启用TTL的Agent模块需要通过Java启动参数添加javaagent来激活字节码增强功能。TTL通过Instrumentation回调激发ClassFileTransformer,实现目标类的字节码增强,从而在执行任务时自动完成上下文的捕捉和传递。
TTL框架提供了一种高效、灵活的方式来解决线程池中线程复用时上下文传递的问题。通过委托机制和字节码增强技术,TTL实现了无入侵地提供线程本地变量传递功能。如果您在业务代码中遇到异步执行时上下文传递的问题,TTL库是一个值得考虑的解决方案。
UE4源码剖析——异步与并行 中篇 之 Thread
我们知道UE中的异步框架分为TaskGraph与Thread两种,上篇教程我们学习了TaskGraph,它擅长处理有依赖关系的短任务;本篇教程我们将学习Thread,它与TaskGraph相反,尚硅谷微信支付源码它更擅长于处理长任务。而下一篇文章,我们则会承接Thread,去学习一下引擎中一些重要的线程。
Thread擅长处理长任务,从长任务生命周期这个层面来看,我们可以先把长任务分为两类:常驻型长任务与非常驻型长任务。
常驻型长任务侧重于并行,通常用于监听式服务,例如网络传输,使用单独的线程对网络进行监听,每当有网络数据包到达时,线程接收并处理后,不会立即结束,而是重置部分状态,继续监听,等待下一轮数据包。
非常驻型长任务侧重于异步,通常用于数据处理,例如主线程为了提高性能,避免卡顿,会将一些重负载的运算任务分发给分线程处理,可能分批给多条分线程,主线程继续运行其他逻辑。任务处理完成后,将结果返回给主线程,分线程可销毁。
接下来,我们通过两个例子学习Thread的使用。
计算由N到M(N和M为大数字)所有数字的和。使用Thread异步调用,将计算操作交由分线程执行,计算完成后再通知主线程结果,代码实现如下:
逻辑分为两部分:启动分线程计算数字和,使用Async函数,参数为EAsyncExecution::Thread,创建新线程执行。学习Async函数用法,该函数返回TFuture对象,代表未来状态,rpm2cpio源码编译当前无法获取结果,但在未来某个时刻状态变为Ready,此时可通过TFuture获取结果。
主线程注册回调,等待分线程计算完成,使用TFuture的Then函数,完成时触发注册的回调,也可使用Wait系列函数等待计算完成。
接下来学习常驻型任务使用。
定义玩家血量上限点,当前点,当血量未满时,每0.2秒恢复1点血量。代码实现分为创建生命治疗仪FRunnable对象、重写Run函数、创建FRunnableThread线程、测试恢复功能和释放线程资源。
生命治疗仪创建与测试完整代码如下,可验证生命恢复功能和暂停与恢复。
UE4中的FRunnable与FRunnableThread提供创建常驻型任务所需接口。无论是常驻型还是非常驻型,底层实现相同,都是使用FRunnableThread线程。
FRunnableThread线程结构包含标识符、逻辑功能、效率与性能、辅助调试字段。线程创建与生命周期分为创建FRunnable类对象、创建FRunnableThread对象两步,通过FRunnable的生命周期管理实现线程运行与停止。
UE4线程管理流程包括继承并创建FRunnable类对象、创建FRunnableThread对象,生命治疗仪线程创建代码。
UE4中的几种异步方式底层使用线程实现,学习了线程类型、创建、生命周期、销毁方法,为下篇学习引擎特殊线程打下基础。
ãPocoç¬è®°ã线ç¨Thread
PocoçThreadæ¯å¯¹æ ååºstd::threadçå°è£ ï¼åæ¶å®ç±»ä¼¼Javaä¸æ ·ï¼æä¾äºRunnableæ¥å£ãæ以使ç¨ä¸æ¯å¯¹æ Javaçãä¸æ ååºä¸åçæ¯ï¼Poco::Threadå建åè¿è¡æ¶ç¸å离çãè¿ä¸ç¹æ ååºè®¾è®¡ç¡®å®ä¸å¤ªå好ãä¾å¦ä¸é¢ä¾åã
åæ ·çä¾å
ç±ä¸é¢å¯è§ï¼ä½¿ç¨åºæ¬è·Java类似ãå建ä¸è¿è¡ä¹å离äºã
çä¸ä¸ä¸»è¦çè¿è¡æ¥å£ï¼æèªPoco1.9æºç
æºç æ件主è¦å å«
1.Thread.h/Thread.cpp
æä¾å¤é¨è°ç¨æ¥å£
å¨Thread.cppä¸å®ä¹äºä¸¤ç§Holder,TP5小说站源码 RunnableHolderåCallableHolderãHolderææ¯æ¯Pocoæ¡æ¶ä¸ç»å¸¸ç¨å°çï¼æ¯å¯¹æä¸ç§ç±»å对象çæéå è£ ã
Runnable为线ç¨è¿è¡ç±»çåºç±»ï¼
Callable为带ä¸ä¸ªåæ°çæ¹æ³
2.Thread_POSIX.h/Thread_POSIX.cpp
3.Thread_VX.h/Thread_VX.cpp
4.Thread_WIN.h/Thread_WIN.cpp
5.Thread_WINCE.h/Thread_WINCE.cpp
è¿å 个æ件ï¼æ¯ä¸ªæ件ä¸é½å®ä¹äºThreadImplï¼ç¨äºä¸åå¹³å°ä¸çå ·ä½å®ç°ï¼Threadç§æ继æ¿ThreadImpï¼ThreadImpç¨äºåªä¸ä¸ªæ件ç±ç¼è¯å®å³å®ã
顺便说ä¸ä¸POSIXç³»ç»ä¸çå®ç°ãå 为使ç¨çæ¯c++ï¼å½æ¶æ²¡æthreadç±»ï¼æ以ææçå®ç°é½æ¯ä½¿ç¨pthreadåºæ¥å®ç°çãå ·ä½ç使ç¨è¯·åèpthreadææ¯ææ¡£ã
6.ThreadLocal.h/ThreadLocal.cpp
ThreadLocalä¸å®ä¹äºä¸ä¸ªç±»ï¼ TLSAbstractSlotç±»ï¼ TLSSlotç±»ï¼ ThreadLocalStorageç±»
TLSAbstractSlotæ¯åºç±»ï¼TLSSlotæ¯æ¨¡æ¿ç±»ï¼éè¿æ¨¡æ¿ææ¯å 裹äºå ·ä½çç±»åãThreadLocalStorageæ¯ç¨äºçº¿ç¨åå¨ï¼å ·ä½æ¯éè¿ä¸ä¸ªmapæ¥å®ç°ã
å 为1.9使ç¨çæ¯c++ï¼è¿æ²¡æå¼ç¨local_threadå ³é®åï¼æ以è¿éæ¯éè¿è¿ç§æ¹å¼å®ç°ã
ThreadLocalStorageå®ä¹å¦ä¸
é£ä¹Poco::Threadçtlsæ¯å¦ä½å®ä¹çï¼
æºç æ件æ¯è¾å°ï¼ä¸»è¦å¦ä¸æ件
1.Thread.h/Thread.cpp
2.Thread_STD.h/Thread_POSIX.cpp/Thread_VX.cpp/Thread_WIN.cpp
Thread.h 主è¦å¯¹å®ç°ç±»ThreadImpçå è£ ï¼å¹¶å®ä¹äºå¯¹å¤æ¥å£ã
Thread_STD.hå®ä¹äºå é¨å®ç°,主è¦æä¾äºThreadImpç±»
Thread_POSIX.cpp/Thread_VX.cpp/Thread_WIN.cppåå«å®ä¹ä¸åå¹³å°ä¸çå ¼å®¹å®ç°
å¨Thread_STD.hä¸å®ä¹äºå 个éè¦ç±»å
å¨Thread.cppä¸å¢å äºä¸¤ç§
private修饰çThreadDataï¼å®ä¹äºçº¿ç¨å é¨æ°æ®ã 1.9ä¸æºç åå«å®ä¹å¨å个平å°å®ç°ç±»ä¸ï¼è¿éæ½ç¦»åºæ¥å®ä¹å¨Thread.cppä¸ãè¾ä¹åçå®ä¹ï¼è¿éé¢å¤çæ¯æ°å¢äºstd::threadæéãå 为ç´æ¥å¼ç¨äºc++ä¸çthreadï¼æäºå®ç°ç´æ¥åå©äºå®ã
硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理
深入剖析JUC线程池ThreadPoolExecutor的执行核心 早有计划详尽解读ThreadPoolExecutor的源码,因事务繁忙未能及时整理。在之前的文章中,我们曾提及Doug Lea设计的Executor接口,其顶层方法execute()是线程池扩展的基础。本文将重点关注ThreadPoolExecutor#execute()的实现,结合简化示例,逐步解析。 ThreadPoolExecutor的核心功能包括固定的核心线程、额外的非核心线程、任务队列和拒绝策略。它的设计巧妙地运用了JUC同步器框架AbstractQueuedSynchronizer(AQS),以及位操作和CAS技术。以核心线程为例,设计上允许它们在任务队列满时阻塞,或者在超时后轮询,而非核心线程则在必要时创建。 创建ThreadPoolExecutor时,我们需要指定核心线程数、最大线程数、任务队列类型等。当核心线程和任务队列满载时,会尝试添加额外线程处理新任务。线程池的状态控制至关重要,通过整型变量ctl进行管理和状态转换,如RUNNING、SHUTDOWN、STOP等,状态控制机制包括工作线程上限数量的位操作。 接下来,我们深入剖析execute()方法。首先,方法会检查线程池状态和工作线程数量,确保在需要时添加新线程。这里涉及一个疑惑:为何需要二次检查?这主要是为了处理任务队列变化和线程池状态切换。任务提交流程中,addWorker()方法负责创建工作线程,其内部逻辑复杂,包含线程中断和适配器Worker的创建。 Worker内部类是线程池核心,它继承自AQS,实现Runnable接口。Worker的构造和run()方法共同确保任务的执行,同时处理线程中断和生命周期的终结。getTask()方法是工作线程获取任务的关键,它会检查任务队列状态和线程池大小,确保资源的有效利用。 线程池关闭操作通过shutdown()、shutdownNow()和awaitTermination()方法实现,它们涉及线程中断、任务队列清理和状态更新等步骤,以确保线程池的有序退出。在这些方法中,可重入锁mainLock和条件变量termination起到了关键作用,保证了线程安全。 ThreadPoolExecutor还提供了钩子方法,允许开发者在特定时刻执行自定义操作。除此之外,它还包含了监控统计、任务队列操作等实用功能,每个功能的实现都是对execute()核心逻辑的扩展和优化。 总的来说,ThreadPoolExecutor的execute()方法是整个线程池的核心,它的实现原理复杂而精细。后续将陆续分析ExecutorService和ScheduledThreadPoolExecutor的源码,深入探讨线程池的扩展和调度机制。敬请关注,期待下文的详细解析。Qt——QThread源码浅析
在探索Qt的多线程处理中,QThread类的实现源码历经变迁。在Qt4.0.1和Qt5.6.2版本中,尽管QThread类的声明相似,但run()函数的实现有所不同。从Qt4.4开始,QThread不再是抽象类,这标志着一些关键调整。
QThread::start()函数在不同版本中的核心代码保持基本一致,其中Q_D()宏定义是一个预处理宏,用于获取QThread的私有数据。_beginthreadex()函数则是创建线程的核心,调用QThreadPrivate::start(this),即执行run()函数并发出started()信号。
QThread::run()函数在Qt4.4后的版本中,不再强制要求重写,而是可以通过start启动事件循环。在Qt5.6.2版本中,run函数的定义更灵活,可以根据需要进行操作。
关于线程停止,QThread提供了quit()、exit()和terminate()三种方式。quit()和exit(0)等效,用于事件循环中停止线程,而terminate()则立即终止线程,但不推荐使用,因为它可能引发不稳定行为。
总结起来,QThread的核心功能包括线程的创建、run函数的执行以及线程的结束控制。从Qt4.4版本开始,QThread的使用变得更加灵活,可以根据需要选择是否重写run函数,以及如何正确地停止线程。不同版本间的细微差别需要开发者注意,以确保代码的兼容性和稳定性。
InheritableThreadLocal源码剖析
InheritableThreadLocal是Java中用于在多线程环境共享数据的工具,它允许子线程继承父线程的值,从而避免了线程间数据同步的复杂性。与ThreadLocal不同,InheritableThreadLocal实现了数据的继承机制,确保了数据在父线程到子线程间的顺利传递。这使得在使用线程池或其他线程管理技术时,应用程序能够保持数据的一致性和完整性。
InheritableThreadLocal提供了一种从父线程到子线程的数据传递方式,它通过在Thread类中引入了inheritableThreadLocals字段来实现这一功能。这一字段是一个ThreadLocalMap类型的对象,专门用于存储InheritableThreadLocal实例。这意味着当创建子线程时,它会自动接收并继承父线程的值。
实现这一特性,InheritableThreadLocal主要通过三个关键方法:set、get、remove。它们与ThreadLocal的同名方法相似,但操作的内部数据结构有所不同。InheritableThreadLocal的set、get、remove方法会通过获取inheritableThreadLocals字段中的ThreadLocalMap对象来进行操作,而ThreadLocal则通过操作threadLocals字段。
为了验证InheritableThreadLocal的继承机制,可以通过在父线程中设置InheritableThreadLocal的值,然后在子线程中尝试获取该值来观察结果。实验证明,子线程能够成功获取到父线程设置的值,证明了InheritableThreadLocal的继承功能。
在使用InheritableThreadLocal时,需要注意的是它的内存管理。一旦线程创建了InheritableThreadLocal实例,它会一直保留在所有后代线程中,直到显式调用remove方法或线程结束。因此,在资源管理和内存控制上,开发者需要特别注意,以防止潜在的内存泄漏问题。
总之,InheritableThreadLocal通过在Thread类中引入专门的数据结构和方法来实现其独特的继承机制,简化了多线程编程中数据共享和管理的复杂性。然而,其使用需要谨慎,以避免不必要的内存占用和潜在的内存泄漏风险。
ThreadPoolExecutor简介&源码解析
线程池是通过池化管理线程的高效工具,尤其在多核CPU时代,利用线程池进行并行处理任务有助于提升服务器性能。ThreadPoolExecutor是线程池的具体实现,它负责线程管理和任务管理,以及处理任务拒绝策略。这个类提供了多种功能,如通过Executors工厂方法配置,执行Runnable和Callable任务,维护任务队列,统计任务完成情况等。
创建线程池需要考虑关键参数,如核心线程数(任务开始执行时立即创建),最大线程数(任务过多时限制新线程生成),线程存活时间,任务队列大小,线程工厂以及拒绝策略。JDK提供了四种拒绝策略,如默认的AbortPolicy,当资源饱和时抛出异常。此外,线程池还提供了beforeExecute和afterExecute钩子函数,用于在任务执行前后执行自定义操作。
当任务提交到线程池时,会经历一系列处理流程,包括任务的执行和线程池状态的管理。例如,如果任务队列和线程池满,会根据拒绝策略处理新任务。使用线程池时,需注意线程池容量与状态的计算,以及线程池工作线程的动态调整。
示例中,自定义线程池并重写钩子函数,创建任务后向线程池提交,可以看到线程池如何根据配置动态调整资源。但要注意,如果任务过多且无法处理,可能会抛出异常。源码分析中,submit方法实际上是调用execute,而execute内部包含Worker类和runWorker方法的逻辑,包括任务的获取和执行。
线程池的容量上限并非Integer.MAX_VALUE,而是由ctl变量的低位决定。 Doug Lea的工具函数简化了ctl的操作,使得计算线程池状态和工作线程数更加便捷。通过深入了解ThreadPoolExecutor,开发者可以更有效地利用线程池提高应用性能。
面试官:Thread启动线程的start方法能执行多次吗?
在Java中,线程的创建与启动机制是通过Thread类中的start方法来实现的,而非直接调用run方法。这是基于线程状态管理的必要性。线程在其生命周期中会经历NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED等多个状态。start方法的作用是将线程从NEW状态转变为RUNNABLE状态,然后等待系统资源分配,一旦获得执行机会,便会执行run方法中的任务,实现真正的多线程工作。
直接调用run方法的情况不同,它会将run方法视为main线程中的普通函数执行,无法在新的线程中启动,因此不能用来启动线程。如果我们尝试多次调用start方法,只有第一次会成功启动线程,后续调用会抛出IllegalThreadStateException异常,因为线程的状态已经变为非初始状态,不能再调用start方法。
以下是start方法和run方法的源码理解:
start方法会检查线程状态,如果状态不是初始态,就会抛出异常。而run方法本身不触发线程的创建,仅在start方法调用后被执行。
总结来说,start方法是启动线程的关键,它确保了线程的生命周期管理和正确执行,而run方法则是线程实际执行的任务。理解这些原理对于正确使用和管理Java线程至关重要。
2024-11-30 07:46
2024-11-30 07:40
2024-11-30 07:32
2024-11-30 07:16
2024-11-30 05:42
2024-11-30 05:17