合理使用线程池以及线程变量

合理使用线程池以及线程变量,第1张

此次需求做的是同步功能,将第三方数据拉取到本地

1、同步将由多线程进行,节省时间

2、拉取过来后将与本地表进行对比:add、update、delete三种情况

3、本地数据修改后,将通过rabbitmq形式发送消息到其他服务进行业务处理。

通过submit 方法能够获取线程的返回结果

通过execute方法不会获取线程返回

在此 我使用了submit方法来执行多线程任务

一般来说Future和Callable是搭配在一起使用的

get方法为阻塞式超过最大等待时间将抛出异常

简单说 ThreadLocal 就是一种以 空间换时间 的做法,在每个 Thread 里面维护了一个以 开放定址法 实现的ThreadLocalThreadLocalMap,把数据进行隔离,数据不共享,自然就没有 线程安全 方面的问题了。

JDK12 就提供了javalangThreadLocal。ThreadLocal 为解决 多线程程序 的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序,ThreadLocal 并不是一个 Thread ,而是 Thread 的局部变量

ThreadLocal 用于保存某个线程共享变量:对于同一个 static ThreadLocal,其为每个使用该变量的线程提供独立的变量副本,不同线程只能从中 get、set 和 remove 自己的变量,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。ThreadLocal 类接口很简单,只有四个方法:

值得一提的是,在 JDK50 中,ThreadLocal 已经支持 泛型 ,该类的类名已经变为 ThreadLocal<T>。API 方法也相应进行了调整,新版本的 API 方法分别是 void set(T value)、T get() 以及 T initialValue()。

ThreadLocal 如何为每一个线程维护变量的副本?思路很简单:在 ThreadLocal 类中有一个 Map,用于存储每一个线程的变量副本,Map 中元素的键为线程对象,而值对应线程的变量副本。

1️⃣线程共享变量缓存如下: ThreadThreadLocalMap<ThreadLocal, Object>;

调用 ThreadLocalget() 时,实际上是从当前线程中获取 ThreadLocalMap<ThreadLocal, Object>,然后根据当前 ThreadLocal 获取当前线程共享变量 Object 。ThreadLocalset,ThreadLocalremove 实际上是同样的道理。

2️⃣这种存储结构的好处:

3️⃣关于 ThreadLocalMap<ThreadLocal, Object> 弱引用 问题:

当线程没有结束,但是 ThreadLocal 已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object> 的键值对,造成 内存泄露 。(ThreadLocal 被回收,ThreadLocal 关联的线程共享变量还存在。)

虽然 ThreadLocal 的 get/set 方法可以清除 ThreadLocalMap 中 key 为 null 的 value,但是 get/set 方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,有两种手段:

结果如下:

ThreadLocal这个类想必大家都不陌生,直接翻译为 线程本地(变量) ,我们经常会使用到它来保存一些 线程隔离的 全局的 变量信息。使用ThreadLocal维护变量时,每个线程都会获得该线程独享一份变量副本。

ThreadLocal比较像是DNF中的一个地下城副本,而每个线程像是每个进入DNF副本中的玩家。各个线程进入副本后都是比较隔离的,不会互相干扰,这一特性在多线程的某些场景下十分适用。

ThreadLocal将变量的使用范围恰当的保存到了全局变量和局部变量之间。

笔者经常使用ThreadLocal的场景有:

ThreadLocal实现结构以及执行的过程如下图所示。

ThreadLocal的几个关键词。

如前文所述,ThreadLocalMap其实是一个ThreadLocal --> value的映射,具体的实现关系如下图

因此,必须在使用了ThreadLocal的线程执行完后finally中调用 threadLocalremove() ,或者如果 ThreadLocal<HashMap> 的话则调用 threadlocalget()remove() 清空HashMap

在ThreadLocal的使用中,我们经常会需要创建子线程,希望子线程能够继承父线程的ThreadLocal,还是以traceid的使用场景为例,我们创建了子线程来并发处理耗时的逻辑,并且希望子线程中也能如实的打印当前请求的traceid,但是 普通的ThreadLocal在创建新线程后信息会完全丢失 ,笔者曾经在这里踩到过坑。

所以就需要一种方案来复制ThreadLocal到子线程:

[1] ThreadLocal-hash冲突与内存泄漏

[2] ThreadLocal面试攻略:吃透它的每一个细节和设计原理

[3] 面试官:小伙子,听说你看过ThreadLocal源码?(万字图文深度解析ThreadLocal)

在学习java编程开发语言的过程中,我们掌握了线程与线程池等相关技术知识。今天,北大青鸟北京计算机学院就关于线程安全问题给大家做一个简单的说明和介绍,一起来了解一下吧。

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

什么时候考虑到线程安全:

一个对象是否需要线程安全,取决于该对象是否被多线程访问。这指的是程序中访问对象的方式,而不是对象要实现的功能。要使得对象是线程安全的,要采用同步机制来协同对对象可变状态的访问。Java常用的同步机制是Synchronized,还包括volatile类型的变量,显示锁以及原子变量。在多个线程中,当它们同时访问同个类时,每次执行的结果和单线程结果一致,且变量值跟预期一致,这个类则是线程安全的。

锁的特性

锁机制的两种特性:

互斥性:即同一时间只允许一个线程持有某个对象的锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合 *** 作)进行访问。互斥性我们也往往称为 *** 作的原子性。

可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的,否则另一个线程可能是在本地缓存的某个副本上继续 *** 作从而引起不一致。

挂起、休眠、阻塞和非阻塞

挂起:当线程被挂起时,其会失去CPU的使用时间,直到被其他线程(用户线程或调试线程)唤醒。

休眠:同样是会失去CPU的使用时间,但是在过了指定的休眠时间之后,它会自动激活,无需唤醒(整个唤醒表面看是自动的,但实际上也得有守护线程去唤醒,只是不需编程者手动干预)。

阻塞:在线程执行时,所需要的资源不能得到,则线程被挂起,直到满足可 *** 作的条件。

非阻塞:在线程执行时,所需要的资源不能得到,则线程不是被挂起等待,而是继续执行其余事情,等待条件满足了后,收到了通知(同样是守护线程去做)再执行。

很多场景下应用程序必须能够处理一系列传入请求,简单的处理方式是通过一个线程顺序的处理这些请求,如下图:

单线程策略的优势和劣势都非常明显:

优势:设计和实现简单;劣势:这种方式会带来处理效率的问题,单线程的处理能力是有限,不能发挥多核处理器优势。

在这种场景下我们就需要考虑并发,一个简单的并发策略就是Thread-Per-Message模式,即为每个请求使用一个新的线程。

Thread-Per-Message策略的优势和劣势也非常明显:

优势:设计和实现比较简单,能够同时处理多个请求,提升响应效率;

劣势:主要在两个方面

1资源消耗 引入了在串行执行中所没有的开销,包括线程创建和调度,任务处理,资源分配和回收以及频繁上下文切换所需的时间和资源。2安全

有没有一种方式可以并发执行又可以克服Thread-Per-Message的问题?

采用线程池的策略,线程池通过控制并发执行的工作线程的最大数量来解决Thread-Per-Message带来的问题。可见下图,请求来临时先放入线程池的队列

线程池可以接受一个Runnable或Callable<T>任务,并将其存储在临时队列中,当有空闲线程时可以从队列中拿到一个任务并执行。

反例(使用 Thread-Per-Message 策略)

正例(使用 线程池 策略)

JAVA 中(JDK 15+)线程池的种类:

程序不能使用来自有界线程池的线程来执行依赖于线程池中其他任务的任务。

有两个场景:

要缓解上面两个场景产生的问题有两个简单的办法:

真正解决此类方法还是需要梳理线程池执行业务流程,不要在有界线程池中执行相互依赖的任务,防止出现竞争和死锁。

向线程池提交的任务需要支持中断。从而保证线程可以中断,线程池可以关闭。线程池支持 javautilconcurrentExecutorServiceshutdownNow() 方法,该方法尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行的任务的列表。

但是 shutdownNow() 除了尽力尝试停止处理主动执行的任务之外不能保证一定能够停止。例如,典型的实现是通过Threadinterrupt()来停止,因此任何未能响应中断的任务可能永远不会终止,也就造成线程池无法真正的关闭。

反例:

正例:

线程池中的所有任务必须提供机制,如果它们异常终止,则需要通知应用程序

如果不这样做不会导致资源泄漏,但由于池中的线程仍然被会重复使用,使故障诊断非常困难或不可能。

在应用程序级别处理异常的最好方法是使用异常处理。异常处理可以执行诊断 *** 作,清理和关闭Java虚拟机,或者只是记录故障的详细信息。

也就是说在线程池里执行的任务也需要能够抛出异常并被捕获处理。

任务恢复或清除 *** 作可以通过重写 javautilconcurrentThreadPoolExecutor 类的 afterExecute() 钩子来执行。

当任务通过执行其 run() 方法中的所有语句并且成功结束任务,或者由于异常而导致任务停止时,将调用此钩子。

可以通过自定义 ThreadPoolExecutor 服务来重载 afterExecute()钩子。

还可以通过重载 terminated() 方法来释放线程池获取的资源,就像一个finally块。

反例:

任务意外终止时作为一个运行时异常,无法通知应用程序。此外,它缺乏恢复机制。因此,如果Task抛出一个NullPointerException ,异常将被忽略。

正例:

另外一种方式是使用 ExecutorServicesubmit() 方法(代替 execute() 方法)将任务提交到线程池并获取 Future 对象。

当通过 ExecutorServicesubmit() 提交任务时,抛出的异常并未到达未捕获的异常处理机制,因为抛出的异常被认为是返回状态的一部分,因此被包装在ExecutionException ,并由Futureget() 返回。

javalangThreadLocal 类提供线程内的本地变量。根据Java API

ThreadLocal对象需要关注那些对象被线程池中的多个线程执行的类。

线程池缓存技术允许线程重用以减少线程创建开销,或者当创建无限数量的线程时可以降低系统的可靠性。

当 ThreadLocal 对象在一个线程中被修改,随后变得可重用时,在重用的线程上执行的下一个任务将能看到该线程上执行过的上一个任务修改的ThreadLocal 对象的状态。

所以要在使用线程池时重新初始化的ThreadLocal对象实例。

反例:

DiaryPool类创建了一个线程池,它可以通过一个共享的无界的队列来重用固定数量的线程。

在任何时候,不超过numOfThreads个线程正在处理任务。如果在所有线程都处于活动状态时提交其他任务,则 它们在队列中等待,直到线程可用。

当线程循环时,线程的线程局部状态仍然存在。

下表显示了可能的执行顺序:

时间任务线程池提交方法日期1t11doSomething1()星期五2t22doSomething2()星期一3t31doSomething3()星期五

在这个执行顺序中,期望从doSomething2() 开始的两个任务( t 2和t 3 doSomething2() 将当天视为星 期一。然而,因为池线程1被重用,所以t 3观察到星期五。

解决方案(try-finally条款)

符合规则的方案removeDay() 方法添加到Diary类,并在try‐finally 块中的实现doSomething1() 类的doSomething1() 方法的语句。finally 块通过删除当前线程中的值来恢复threadlocal类型的days对象的初始状态。

如果threadlocal变量再次被同一个线程读取,它将使用initialValue()方法重新初始化 ,除非任务已经明确设置了变量的值。这个解决方案将维护的责任转移到客户端( DiaryPool ),但是当Diary类不能被修改时是一个好的选择。

解决方案(beforeExecute())

使用一个自定义ThreadPoolExecutor 来扩展 ThreadPoolExecutor并覆盖beforeExecute() 方法。beforeExecute() 方法在Runnable 任务在指定线程中执行之前被调用。该方法在线程 “t” 执行任务 “r” 之前重新初始化 threadlocal 变量。

以上就是关于合理使用线程池以及线程变量全部的内容,包括:合理使用线程池以及线程变量、我现在有一个dll,如何“远程线程加载” 如何挂钩本地进程、Future配合线程池进行多线程任务并返回结果等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9691695.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存