Java并发编程 I - 并发问题的源头
Java并发编程 II - 没有共享就没有伤害(ThreadLoacl)
Java并发编程 III - 让共享数据只读(final关键字)
Java并发编程 IV - volatile关键字与Atomic类
Java并发编程 V - 并发的万能钥匙synchronized
Java并发编程 VI - 线程生命周期与线程间的协作
Java并发编程 VII - Lock
局部变量是线程安全的
并发问题都是由于共享数据的竞争造成的。
那为什么局部变量不会造成并发问题呢?
CPU找调用方法的参数和返回地址,是通过堆栈寄存器。CPU支持一种线性结构,因为与方法调用有关,所以也称为调用栈。每个方法在调用栈里都有自己的独立空间,称为栈帧。每个栈帧都有对应方法需要的参数和返回地址。当调用新方法时,会创建新的栈帧,并压入调用栈;当方法返回时,对应的栈帧就会被自动弹出。即,栈帧和方法同生共死。局部变量就是存放到调用栈中的。
每个线程都有自己独立的调用栈。所以,Java方法里面的局部变量是不存在并发问题的。
ThreadLocal
ThreadLocal能够让数据线程唯一,防止数据共享。
ThreadLocal怎么用
static ThreadLocal<Integer> tl = new ThreadLocal();
public static void main(String[] args) throws InterruptedException {
tl.set(100);
new Thread(() -> {
System.out.println("thread === " + tl.get());
}).start();
System.out.println("main === " + tl.get());
}
/*
输出 ->
main === 100
thread === null
*/
ThreadLocal设计思想
1、Thread 内部持有 ThreadLocalMap<ThreadLocal(弱引用), T (强引用)>对象threadLocals
2、ThreadLocal内部
get() -> 获取当前thread对象 -> 获取threadLocals(并且判空,为空就为该thread创建threadLocals)-> map.get(this) 获取到value
set(T value) -> 获取当前thread对象 -> 获取threadLocals(并且判空,为空就为该thread创建threadLocals)-> map.set(this, value)
remove() -> 获取当前thread对象 -> 获取threadLocals -> map.remove(this)
3、为什么Java这样设计(防止内存泄漏)
ThreadLocal生命周期要比Thread要长,如果ThreadLocal一直引用这Thread的话,导致这个Thread一直不能被释放。而且 ThreadLocalMap 里对 ThreadLocal 的引用还是弱引用,所以只要 Thread 对象可以被回收,那么 ThreadLocalMap 就能被回收。
ThreadLocal 在线程池中的使用,存在内存泄漏的风险
原因就出在线程池中线程的存活时间太长,往往都是和程序同生共死的,这就意味着 Thread 持有的 ThreadLocalMap 一直都不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用(WeakReference),所以只要 ThreadLocal 结束了自己的生命周期是可以被回收掉的。但是 Entry 中的 Value 却是被 Entry 强引用的,所以即便 Value 的生命周期结束了,Value 也是无法被回收的,从而导致内存泄露。
解决办法:
ExecutorService es;
ThreadLocal tl;
es.execute(()->{
tl.set(obj);//ThreadLocal增加变量
try {
// 省略业务逻辑代码
}finally {
tl.remove();//手动清理ThreadLocal
}
});
避免共享变量的两种解决方案,使用局部变量会频繁创建对象,使用threadlocal也是针对线程创建新变量,都是针对线程维度,threadlocal并未体现出什么优势,为什么还要用threadlocal
回答:threadlocal=线程数,局部变量=调用量,差距太大了