ThreadLocal从入门到精通--彻底掌握
本文带领你从各个方面了解并掌握ThreadLocal,进而彻底精通。
使用场景
1.每个线程需要一个独享的对象(不安全的工具类)
2.每个线程需要保存全局变量(可让不同的方法调用)
以下实例代码对共享的操作添加了类锁,会有性能问题,见下图
package threadPool;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试不使用ThreadLocal时如何对线程的共享变量加锁
*/
public class NotUseThreadLocalTest01 {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
public static void main(String[] args) {
//创建线程池去执行1000个任务
ExecutorService executorService= Executors.newFixedThreadPool(10);
NotUseThreadLocalTest01 threadLocalTest01=new NotUseThreadLocalTest01();
for (int i=0;i<1000;i++){
int finalI = i;
executorService.submit(new Runnable() {
@Override
public void run() {
//获取当前时间戳
System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI));
}
});
}
executorService.shutdown();
}
public String getDateStrFromSeconds(int seconds){
Date date=new Date(seconds*1000);
String dateStr=null;
//加类锁
synchronized (NotUseThreadLocalTest01.class){
dateStr=simpleDateFormat.format(date);
}
return dateStr;
}
}
以下实例代码我们使用threadlocal去生成dateformat,见下文。
package threadPool;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 测试ThreadLocal时如何对线程的共享变量加锁
*/
public class UseThreadLocalTest02 {
public static void main(String[] args) {
//创建线程池去执行1000个任务
ExecutorService executorService= Executors.newFixedThreadPool(10);
UseThreadLocalTest02 threadLocalTest01=new UseThreadLocalTest02();
for (int i=0;i<1000;i++){
int finalI = i;
executorService.submit(new Runnable() {
@Override
public void run() {
//获取当前时间戳
System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI));
}
});
}
executorService.shutdown();
}
public String getDateStrFromSeconds(int seconds){
Date date=new Date(seconds*1000);
return DateFormatter.simpleDateFormatThreadLocal.get().format(date);
}
}
class DateFormatter{
public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal=new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
};
//也可以使用lamda初始化
public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocalWithLamda=ThreadLocal.withInitial(()->
new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")
);
}
以下实现,我们对线程中不同的方法基于threadlocal传递参数,见下图
package threadPool;
/**
* 实现主线程,基于threadlocal传递参数
*/
public class UserThreadLocal03 {
public static void main(String[] args) {
try{
UserThreadLocal03 userThreadLocal03=new UserThreadLocal03();
userThreadLocal03.serviceOne();
}finally {
ThreadLocalParams.userThreadLocal.remove();
}
}
public void serviceOne(){
ThreadLocalParams.userThreadLocal.set(new User("123"));
serviceTwo();
}
public void serviceTwo(){
System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId());
serviceThree();
}
public void serviceThree(){
System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId());
}
}
class User
{
private String id;
public User(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
class ThreadLocalParams{
public static ThreadLocal<User> userThreadLocal=new ThreadLocal<User>();
}
ThreadLocal方法介绍
1.initialValue:初始化
2.get 获取当前线程threadlocal对应的值
3.set 设置当前线程threadLocal的值
4.remove移除
ThreadLocal部分源码分析
主要分析initialValue、get、set等
get方法如下
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();
}
get方法首先会从当前threadlocalMap中获取,如果有了,直接返回,若没有的时候,则会调用initialValue方法后返回,则说明initialValue这个方法是延迟加载的,只有在用的时候才会加载,接下面我们查看下set方法的部分实现。
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);
}
从上面代码中我们发现set方法最重要的调用方法为map.set,将线程变量的值进行写入,接下面我们查看下initialValue方法,
initialValue方法部分实现如下图所示:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
我们发现initialValue方法最终调用的也是map.set方法,进而可知,initialValue和set的实现有异曲同工之妙。
ThreadLocal内存泄漏问题
我们查看remove代码可以看到下面这样的实现,会执行clear操作,所有在线程池中传递变量后,不再使用时,需要显示执行remove等类似方法进行清除。
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
.............
public void clear() {
this.referent = null;
}
.............
private T referent; /* Treated specially by GC */
ThreadLocal优点
1.解决线程安全问题
2.减少内存的使用
3.避免或者减少线程方法中的方法传递
ThreadLocal空指针异常
详细介绍空指针异常。threadLocal本身没有空指针异常, 多数情况是类型进行自动装箱时导致的,可以看下实例代码,如下所示:
package threadPool;
public class ThreadLocalNullPointExceptionTest {
private ThreadLocal<Long> threadLocal= new ThreadLocal<Long>();
public void set(){
threadLocal.set(Thread.currentThread().getId());
}
/**
* 空的null转成long的时候会出现空指针异常
* Exception in thread "main" java.lang.NullPointerException
* at threadPool.ThreadLocalNullPointExceptionTest.get(ThreadLocalNullPointExceptionTest.java:9)
* at threadPool.ThreadLocalNullPointExceptionTest.main(ThreadLocalNullPointExceptionTest.java:14)
* @return
*/
public long get(){
return threadLocal.get();
}
public static void main(String[] args) {
ThreadLocalNullPointExceptionTest threadLocalNullPointExceptionTest=new ThreadLocalNullPointExceptionTest();
System.out.println(threadLocalNullPointExceptionTest.get());
}
}

浙公网安备 33010602011771号