在51CTO的第一篇博文--ThreadLocal
发布时间:2023-02-14 18:14:52 274 相关标签: # java# java# 数据# 脚本# 技术
一、自我介绍
hello!大家好,本人是一名Java程序员,热爱生活、热爱学习、希望能在51博客结实志同道合的猿类。
二、新年计划
22年已去,23年已至。看到圈内很多优秀的大佬在开工第一天就开始制定自己全年的计划了,果然越优秀的人越努力。以前都是随着项目用到什么技术才去学习使用,但仅仅是使用还不是深入去理解。给我的感觉是我始终在原地踏步或是龟速前进。所以今年开始尝试改变自己,先从年度计划开始入手,为了防止计划制定有偏差,我先从半年计划开始制定,这样即使有偏差也不会偏的太狠。
- 2月:学习K8S、Jenkins相关构建流程、学会编写基本Shell脚本
- 3月~4月:学习计算机网络原理
- 5月:初步了解JVM
- 6月:学会基础Linux操作命令和理论知识
三、技术分享
今日想跟大家分享的是ThreadLocal这个类,平时在使用多线程的时候我们最担心的是对象的线程安全问题。好在JDK提供了ThreadLocal这个类。从名字上看这是一个线程的局部变量。也就是说,只有当前线程可以访问。可想而知那自然是线程安全的。
ThreadLocal的简单使用
public class ThreadLocalTest {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
public void run() {
try {
Date t = sdf.parse("2023-02-06 17:29:29");
System.out.println(i + "---->" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
上面这段代码在多线程环境中运行有可能会出现异常,因为SimpleDateFormat.parse()方法并不是线程安全的。要想不出错,一种方案是在sdf.parse()方法前后加锁,另一种则是用ThreadLocal为每一个线程创建一个SimpleDateFormat,我们稍微改动一下代码
public class ThreadLocalTest {
private static Thread SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
public void run() {
try {
if (tl.get()==null){
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date t = tl.get().parse("2023-02-06 17:29:29");
System.out.println(i + "---->" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
ThreadLocal实现原理
ThreadLocal是如何保证这些对象只被当前线程访问的呢?我们深入ThreadLocal的源码,主要关注set()和get()方法,先看set()方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
可以看到其思想是先获取当前线程,然后拿到线程的ThreadLocalMap,并将值存在ThreadLocalMap中。看它以Map结尾可以简单将它理解为一个Map,key为ThreadLocal当前对象,value就是我们需要的值。
在进行get()方法操作时,就是从这个Map中把数据取出来。
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();
}
ThreadLocalMap是维护在Thread类内部的,这意味着只要线程不退出,对象的引用将一直存在。当线程退出时,Thread类会进行清理工作,其中就包括ThreadLocalMap。需要特别注意的是,如果你使用了线程池就意味着当前线程未必会退出,执行完毕后这个对象将不再有用,也无法被回收。这个时候如果希望及时回收对象,最好使用ThreadLocal.remove()方法将这个变量移除。
private void exit() {
if (threadLocals != null && TerminatingThreadLocal.REGISTRY.isPresent()) {
TerminatingThreadLocal.threadTerminated();
}
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
文章来源: https://blog.51cto.com/u_11123051/6040166
特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报