ThreadLocal的作用、原理、存在问题以及应用场景

您所在的位置:网站首页 签到方法主要有哪些 ThreadLocal的作用、原理、存在问题以及应用场景

ThreadLocal的作用、原理、存在问题以及应用场景

2024-06-08 14:15| 来源: 网络整理| 查看: 265

1. 什么是ThreadLocal以及它的作用

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果实现每个线程都有自己的专属变量该如何设置?ThreadLocal类主要就是解决让每个线程绑定自己的专属变量。 ThreadLocal主要用来存储当前上下文的变量信息,他可以保障存储进去的信息只能被当前的线程读取到,并且线程之间不会受到影响。ThreadLocal为变量在每个线程都创建了一个副本,那么每个线程可以访问自己的内部的副本变量。

2. ThreadLocal的原理 2.1 源码

我们可以看到ThreadLocal中的set()、get()方法都是调用了ThreadLocalMap中的set()、get()方法。最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThreadLocalMap的map,这个map就是用来存储与这个线程绑定的变量,map的key就是ThreadLocal对象,value就是线程正在执行的任务中的某个变量的包装类Entry。

public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 3. ThreadLocal引发的新问题 3.1 内存泄露问题

ThreadLocalMap中的key是弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清空掉,而value不会。ThreadLocal中就会出现key为null的Entry。假如我们不采取措施,value永远不会被GC回收,这个时候就可能会产生内存泄漏问题。

3.2 解决思路

ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法时会处理掉key为null的记录。使用ThreadLocal时,最好手动调用remove()方法。

4. ThreadLocal的应用场景 数据库事务:通过AOP的方式,对执行数据库事务的函数进行拦截。函数开始前,获取connection开启事务并存储在ThreadLocal中,任何用到connection的地方,从ThreadLocal中获取。函数执行完毕,提交事务释放connection。解决线程安全的问题。比如Java7中的SimpleDateFormat不是线程安全的,可以用ThreadLocal来解决这个问题。 ackage fuxi.ThreadLocal; import java.text.SimpleDateFormat; import java.util.Random; public class ThreadLocalExample implements Runnable{ private static final ThreadLocalformatter=ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM--dd :HH-mm")); public static void main(String[]args){ ThreadLocalExample obj=new ThreadLocalExample(); for(int i=0;i Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } t.start(); } } @Override public void run() { System.out.println("Thread Name="+Thread.currentThread().getName()+" "+"default formatter="+formatter.get().toPattern()); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } formatter.set(new SimpleDateFormat()); System.out.println("Thread Name="+Thread.currentThread().getName()+" "+"formatter="+formatter.get().toPattern()); } } 用户登录信息等:用户登录信息好多个方法上都要用到,给每个方法都添加一个User非常麻烦,而且有些时候,如果调用链有无法修改源码的第三方库,User对象就传不进去了。而ThreadLocal可以在一个线程中传递同一个对象。 package fuxi.ThreadLocal; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[]args){ ExecutorService es= Executors.newFixedThreadPool(3); String[] users = new String[] { "Bob", "Alice", "Tim", "Mike", "Lily", "Jack", "Bush" }; for (String user : users) { es.submit(new Task(user)); } es.shutdown(); } } class Task implements Runnable{ private final String name; public Task(String name){ this.name=name; } @Override public void run() { try(UserContext ctx=new UserContext(this.name)){ new Task1().process(); new Task2().process(); new Task3().process(); } } } class Task1{ public void process(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("[%s] check user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser()); } } class Task2{ public void process(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("[%s] check user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser()); } } class Task3{ public void process(){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("[%s] check user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser()); } } class UserContext implements AutoCloseable{ private static final ThreadLocal userThreadLocal = new ThreadLocal(); public UserContext(String name){ userThreadLocal.set(name); System.out.printf("[%s] init user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser()); } public static String getCurrentUser(){ return userThreadLocal.get(); } @Override public void close() { System.out.printf("[%s] cleanup for user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser()); userThreadLocal.remove(); } } 5. 使用ThreadLocal应该注意什么?

在使用ThreadLocal对象,尽量使用static,不然会使线程的ThreadLocalMap产生太多Entry,从而造成内存泄露。

参考资料:

https://www.iteye.com/blog/liudeh-009-1934768 https://www.liaoxuefeng.com/wiki/1252599548343744/1306581251653666



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3