南京营销型网站建设公司,路由器带u盘接口的做网站,个人做二次元网站怎么赚钱,建设部网站执业资格一、常见场景
1、ThreadLocal作为线程上下文副本#xff0c;那么一种最常见的使用方式就是用来方法隐式传参#xff0c;通过提供的set()和get()两个public方法来实现在不同的方法中的参数传递。对于编程规范来说#xff0c;方法定义的时候是对参数个数是有限制的#x…一、常见场景
1、ThreadLocal作为线程上下文副本那么一种最常见的使用方式就是用来方法隐式传参通过提供的set()和get()两个public方法来实现在不同的方法中的参数传递。对于编程规范来说方法定义的时候是对参数个数是有限制的甚至在一些大厂对方法参数个数是有明确规定的。
2、线程安全每个线程维持自己的变量以免紊乱像常用的数据库的连接池的线程安全实现就使用了ThreadLocal。
二、进阶使用
以参数传递为例子如何更好地使用ThreadLocal来实现在同一线程栈中不同方法中的参数传递。在参数传递的时候那么都会有参数名参数值而ThreadLocal提供的get()和set()方法不能直接满足设置参数名和参数值。这种情况下就需要对ThreadLocal进一次封装如下代码维护一个map对象然后提供setValue(key, value)和getValue(key, value)方法就可以很方便地实现了参数的设置和获取在需要的地方对参数进行清理使用remove(key)或者clear()即可实现。
import java.util.HashMap;
import java.util.Map;public class ThreadLocalMangerT extends ThreadLocalT {private static ThreadLocalMangerMapString, Object MANGER new ThreadLocalManger();private static HashMapString, Object MANGER_MAP new HashMap();public static void setValue(String key, Object value) {MapString, Object context MANGER.get();if(context null) {synchronized (MANGER_MAP) {if(context null) {context new HashMap();MANGER.set(context);}}}context.put(key, value);}public static Object getValue(String key) {MapString, Object context MANGER.get();if(context ! null) {return context.get(key);}return null;}public static void remove(String key) {MapString, Object context MANGER.get();if(context ! null) {context.remove(key);}}public static void clear() {MapString, Object context MANGER.get();if(context ! null) {context.clear();}}
}三、使用漏洞
继续以参数传递为例子来看看ThreadLocal使用过程中存在的问题和后果。在实际业务的功能开发中为了提升效率大部分情况下都会使用线程池来实现比如数据库的连接池、RPC请求连接池、MQ消息处理池、后台批量job池等等同时也可能会使用一个伴随整个应用生命周期的线程守护线程来实现的一些功能比如说心跳、监控等等。使用线程池那么在实际生产业务中并发肯定不低池中线程就会一直复用守护线程一旦创建那么就会活到应用停机。所以在这些情况下线程的生命周期很长在使用ThreadLocal的时候一定要进行清理不然就会有内存溢出的情况发生。通过以下案例来模拟内存溢出的情况。
通过一个死循环来模拟高并发场景。创建一个10个核心线程数10个最大线程数数60秒空闲时间的、线程名以ThreadLocal-demo-开头的线程池在该场景下将有10个线程来运行运行内容很简单生成一个UUID并将其作为参数key然后设置到线程副本中。
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Service;import java.util.UUID;
import java.util.concurrent.*;Service
public class ThreadLocalService {ThreadFactory springThreadFactory new CustomizableThreadFactory(TheadLocal-demo-);ExecutorService executorService new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue(), springThreadFactory);ExecutorService service new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue());public Object setValue() {for(; ;) {try {Runnable runnable new Runnable() {Overridepublic void run() {String id UUID.randomUUID().toString();// addThreadLocalManger.setValue(id, this is a value);//do something hereThreadLocalManger.getValue(id);// clear()//ThreadLocalManger.clear();}};executorService.submit(runnable);} catch (Exception e) {e.printStackTrace();break;}}return success;}} 以上代码中已把clear()方法注释掉不做清理触发程序稍微将jvm设置低一些跑不久就会报如下OOM。
java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread TheadLocal-demo-9
Exception in thread TheadLocal-demo-8
Exception in thread TheadLocal-demo-6
Exception in thread TheadLocal-demo-10
Exception in thread TheadLocal-demo-7
java.lang.OutOfMemoryError: GC overhead limit exceeded
Exception in thread TheadLocal-demo-5
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceededat com.intellij.rt.debugger.agent.CaptureStorage.insertEnter(CaptureStorage.java:57)at java.util.concurrent.FutureTask.run(FutureTask.java)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded 就会发生严重的内存溢出通过如下debug截图可知设置进去的UUID堆积在内存中逐步变多最终撑爆内存。 在实际的业务场景中需要传递的可能有订单号交易号流水号等等这些变量往往是唯一不重复的、符合案例中的UUID情况在不清理的情况下就会造成应用OOM进而不可用在分布式系统中还能导致上下游系统不可用进而导致整个分布式进去的不可用如果这些信息往往还可能用在网络传输中大消息占有网络带宽严重甚至导致网络瘫痪。所以一个小小的细节就会置整个集群于危险之中那么如何合理化解呢。
四、终阶使用
以上问题在于忘记清理那么如何让清理无感知即不需要清理也没有问题。根因在于线程跑完一次之后没有进行清理所以可提供一个基类线程在线程执行最后对清理进行封装。如下代码。提供一个BaseRunnable抽象基类该类主要如下特点。
1、该类继承Runnable。
2、实现setArg(key, value)和getArg(key)两个方法。
2、在重写的run方式中分为两步第一步调用抽象方法task第二步清理线程副本。
有了以上3个特点继承了BaseRunnable的线程类只需要在实现task方法在task方法中实现业务逻辑参数传递和获取通过setArg(key, value)和getArg(key)两个方法即可实现无需再显示清理。
public abstract class BaseRunnable implements Runnable {Overridepublic void run() {try {task();} finally {ThreadLocalManger.clear();}}public void setArg(String key, String value) {ThreadLocalManger.setValue(key, value);}public Object getArg(String key) {return ThreadLocalManger.getValue(key);}public abstract void task();
}