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对应一个线程,线程之间互相隔离