1.大纲
使用场景
带来的好处
主要方法的介绍
2.两大使用场景
每个线程需要一个独享的对象
每个线程内需要保存全局变量,可以让不同的方法直接使用,避免参数传递的麻烦
一:场景一
1.说明
每个线程都需要一个独享的对象
2.普通的线程运行SimpleDateFormat
package com.jun.juc.threadlocal;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*
*/
public class ThreadLocalDateFormat {
public String date(int seconds){
Date date = new Date(seconds * 1000);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return dateFormat.format(date);
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
String dateStr = new ThreadLocalDateFormat().date(10);
System.out.println("dateStr1:"+dateStr);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
String dateStr = new ThreadLocalDateFormat().date(12);
System.out.println("dateStr2:"+dateStr);
}
}).start();
}
}
效果:
Disconnected from the target VM, address: '127.0.0.1:49859', transport: 'socket' dateStr1:1970-01-01 08:00:10 dateStr2:1970-01-01 08:00:12
3.使用线程池
当上面的两个线程不够用的时候,我们开始考虑线程池。
package com.jun.juc.threadlocal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 开始使用线程池处理
*/
public class ThreadLocalDateFormatWithThreadPool {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public String date(int seconds){
Date date = new Date(seconds * 1000);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return simpleDateFormat.format(date);
}
public static void main(String[] args) {
for (int i=0;i<10;i++){
int finalI= i;
executorService.execute(new Runnable() {
@Override
public void run() {
String dateStr = new ThreadLocalDateFormatWithThreadPool().date(finalI);
System.out.println("dateStr="+dateStr);
}
});
}
executorService.shutdown();
}
}
效果:
Connected to the target VM, address: '127.0.0.1:50826', transport: 'socket' dateStr=1970-01-01 08:00:04 dateStr=1970-01-01 08:00:00 dateStr=1970-01-01 08:00:08 dateStr=1970-01-01 08:00:09 dateStr=1970-01-01 08:00:01 dateStr=1970-01-01 08:00:03 dateStr=1970-01-01 08:00:02 dateStr=1970-01-01 08:00:06 dateStr=1970-01-01 08:00:05 dateStr=1970-01-01 08:00:07 Disconnected from the target VM, address: '127.0.0.1:50826', transport: 'socket'
4.问题
上面需要新建很多个SimpleDateFormat。
然后,我们使用同一个对象。
package com.jun.juc.threadlocal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 开始使用线程池处理
*/
public class ThreadLocalDateFormatWithThreadPoolAndStatic {
private static ExecutorService executorService = Executors.newFixedThreadPool(3);
//提取出来
static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public String date(int seconds){
Date date = new Date(seconds * 1000);
return simpleDateFormat.format(date);
}
public static void main(String[] args) {
for (int i=0;i<20;i++){
int finalI= i;
executorService.submit(new Runnable() {
@Override
public void run() {
String dateStr = new ThreadLocalDateFormatWithThreadPoolAndStatic().date(finalI);
System.out.println("dateStr="+dateStr);
}
});
}
executorService.shutdown();
}
}
效果:
D:jdk1.8.0_144injava -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:51581,suspend=y,server=n -D Connected to the target VM, address: '127.0.0.1:51581', transport: 'socket' dateStr=1970-01-01 08:00:00 dateStr=1970-01-01 08:00:00 dateStr=1970-01-01 08:00:00 dateStr=1970-01-01 08:00:04 dateStr=1970-01-01 08:00:04 dateStr=1970-01-01 08:00:06 dateStr=1970-01-01 08:00:07 dateStr=1970-01-01 08:00:08 dateStr=1970-01-01 08:00:08 dateStr=1970-01-01 08:00:10 dateStr=1970-01-01 08:00:10 dateStr=1970-01-01 08:00:13 dateStr=1970-01-01 08:00:13 dateStr=1970-01-01 08:00:13 dateStr=1970-01-01 08:00:15 dateStr=1970-01-01 08:00:15 dateStr=1970-01-01 08:00:17 dateStr=1970-01-01 08:00:18 dateStr=1970-01-01 08:00:17 dateStr=1970-01-01 08:00:19 Disconnected from the target VM, address: '127.0.0.1:51581', transport: 'socket' Process finished with exit code 0
原因:当线程一进来之后,中断,线程二过来,修改,然后中断,如果,线程一过来,则使用的是线程二修改过得值
5.加锁处理这种问题
package com.jun.juc.threadlocal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 加锁处理线程问题
*/
public class ThreadLocalDateFormatWithSynchronized {
private static ExecutorService executorService = Executors.newFixedThreadPool(3);
//提取出来
static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public String date(int seconds) {
Date date = new Date(seconds * 1000);
String format = "";
synchronized (ThreadLocalDateFormatWithSynchronized.class) {
format = simpleDateFormat.format(date);
}
return format;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
int finalI = i;
executorService.submit(new Runnable() {
@Override
public void run() {
String dateStr = new ThreadLocalDateFormatWithSynchronized().date(finalI);
System.out.println("dateStr=" + dateStr);
}
});
}
executorService.shutdown();
}
}
6.使用ThrealLocal
加锁可以处理上面的问题,但是对性能有损耗
package com.jun.juc.threadlocal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 开始使用线程池处理,使用ThreadLocal
*/
public class DateFormatWithThreadPoolLast {
private static ExecutorService executorService = Executors.newFixedThreadPool(10);
public String date(int seconds){
Date date = new Date(seconds * 1000);
SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
return simpleDateFormat.format(date);
}
public static void main(String[] args) {
for (int i=0;i<10;i++){
int finalI= i;
executorService.execute(new Runnable() {
@Override
public void run() {
String dateStr = new DateFormatWithThreadPoolLast().date(finalI);
System.out.println("dateStr="+dateStr);
}
});
}
executorService.shutdown();
}
}
/**
* 产生安全的SimpleDateFormat
*/
class ThreadSafeFormatter{
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
}
7.总结场景一
有时候,我们不需要总是新建多个对象,就考虑提取出来,这个可能会出现线程安全问题。
然后,第一个反应是加锁。但是加锁会存在性能问题,这个时候,可以考虑使用ThreadLocal,保证每个线程是安全的。
二:场景二
1.使用场景

使用ThreadLocal保存一些业务内容
这些内容在同一个线程内相同,但是不同的线程中使用的业务内容是不同的
在线程的生命周期之内,都通过这个静态的ThreadLocal实例的get方法取得自己set过得对象,避免了将这个对象作为参数传递的麻烦,程序更加优雅
2.方法
强调的是同一个请求内不同方法间的共享
不要重写initialValue()方法,但是必须手动调用set()方法
3.实现
package com.jun.juc.threadlocal.second;
/**
* 多个方法使用同一个参数
*/
public class Transmit {
public static void main(String[] args) {
new Service1().process();
}
}
class User{
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
}
class UserThreadLocalHolder{
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class Service1{
public void process(){
User user = new User("tom");
UserThreadLocalHolder.holder.set(user);
new Service2().process();
}
}
class Service2{
public void process(){
User user = UserThreadLocalHolder.holder.get();
System.out.println("user2:"+user.getName());
// 修改
user = new User("bob");
UserThreadLocalHolder.holder.set(user);
new Service3().process();
}
}
class Service3{
public void process(){
User user = UserThreadLocalHolder.holder.get();
System.out.println("user3:"+user.getName());
UserThreadLocalHolder.holder.remove();
}
}
三:方法说明
1.initialValue
在ThreadLocal第一次get的时候把对象给初始化出来,对象的初始化时机是可以让我们来控制的
2.set
生成的时机不由我们随意的控制,只能使用set进行设置
3.好处
线程安全
不需要加锁,提高了效率
高效的利用内存,节省开销
避免传参的繁琐,耦合低,代码优雅
四:原理
1.原理图

2.ThreadLocalMap
这行代码在Thread中:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
然后,看设置。发现,设置进来的是this为key
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.程序里,哪里使用createMap
初始化
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;
}
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);
}
结论:
刚好对应了上文说的两种场景。
4.initialValue
protected T initialValue() {
return null;
}
结论:
方法会返回当前线程对应的初始化值,所以要进行自己重写该方法
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时,先判断是否有这个数据,没有,则走set初始化
结论:
本方法还是延迟加载,在第一次get访问变量时,才进行调用调用本方法。
如果之前,有了set,才不会调用本方法
结论2:
只会触发一次setInitialValue
5.get
再讲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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
this就是ThrealLocal的引用
get方法会取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal的引用作为参数取出,取出map中属于ThreadLocal的value
6.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);
}
如果之前不是空,则进行覆盖
否则,进行创建
this为key
7.与线程的关系
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以发现,value保存到map中,然后又放到了线程中。
这样就可以解释了,为什么会线程安全了。
8.操作主要是map

9.remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
只能删除自己的数据
五:内存泄漏问题
1.说明
某个对象不再使用,但是占用的内存却不能回收
2.原因
进行设置
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
然后,看到ThreadLocalMap
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
看到使用Entry进行保存:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以发现,这个类继承了WeakReference。
可以发现,key是弱引用的保存,value是强引用
3.弱引用的特点
如果这个对象被弱引用关联,又没有强引用关联,则不会阻止GC,就会被回收
4.泄漏
key被回收了,但是value还没有回收。
ThreadLocalMap就不能被回收,因为每个Entry中都有一个value被强引用
如果,线程一直在,ThreadLocalMap一直不回收,则内存泄漏了。
5.做法
我们需要强制删除value,这样,Entry就可以回收了,然后ThreadLocalMap就可以被回收了
使用remove,可以删除
6.remove
获取本线程的ThreadLocalMap:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
进入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;
}
}
}
进入clear:
public void clear() {
this.referent = null;
}
六:在Spring中的使用
1.说明
这里研究不深,以后看spring继续研究
2.RequestContextHolder
public abstract class RequestContextHolder {
private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
public RequestContextHolder() {
}
。。。。。。
每个http对应一个线程,线程之间互相隔离