提供一个变量的副本,线程的隔离。
spring在事务的部分用到了ThreadLocal,让每个线程保存自己的连接,不在同一连接的话不会形成事务
public class UserThreadLocal {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
//可以在new ThreadLocal的时候进行初始化
@Override
protected Integer initialValue() {
return 1;
}
};
private static ThreadLocal<String> threadLocalString = new ThreadLocal<String>();
private static class NewThread implements Runnable{
int id;
public NewThread(int id){
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---start");
Integer i = threadLocal.get();
i = i+id;
threadLocal.set(i);
threadLocalString.set(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+"---"+threadLocal.get());
System.out.println(Thread.currentThread().getName()+"---"+threadLocalString.get());
//threadLocal.remove();
}
}
public static void main(String[] a){
Thread[] threads = new Thread[5];
for(int i=0;i<threads.length;i++){
threads[i] = new Thread(new NewThread(i));
}
for(int i=0;i<threads.length;i++){
threads[i].start();
}
}
}
/* 输出:
Thread-0---start
Thread-3---start
Thread-0---1
Thread-4---start
Thread-2---start
Thread-1---start
Thread-1---2
Thread-1---Thread-1
Thread-2---3
Thread-4---5
Thread-0---Thread-0
Thread-3---4
Thread-4---Thread-4
Thread-2---Thread-2
Thread-3---Thread-3
可以保存每个变量的副本
*/
无论是get还是set 都会先拿到调用方法的线程,再获取当前线程的数据
threadLocal原理
每个线程有自己的成员变量,这个变量为ThreadLocalMap
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;//每一个线程的ThreadLocal可能有多个,比如上述代码中的threadLocal和threadLocalString
}
每一个线程都会启动一个自己的ThreadLocalMap,以ThreadLocal为键,以内容为值,存入到Entry数组
不论get还是set 都是操作自己线程的东西,与多线程无关
ThreadLocal会引发内存泄露以及线程不安全
1.内存泄露
ThreadLocal原理图:
一个Thread中只有一个ThreadLocalMap,一个ThreadLocalMap中可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中一个的Entry(也就是说:一个Thread可以依附有多个ThreadLocal对象)。
在Entry里用了WeakReference(弱引用),一旦发生gc,线程的ThreadLocal会被回收,栈对象就会指到null,
当发生GC的时候,ThreadLocal被回收,ThreadLocalRef则指向null,因为Entry的key为ThreadLocal,所以Entry失去了key,
每一个线程内部(CurrentThread)都有自己的ThreadLocalMap,ThreadLocalMap持有自己的entry,entry持有自己的value,但是value要通过key访问,即ThreadLocal进行访问,所以在Gc之后,key消失了,所以value就无法被访问到了(上面这一系列为强引用),因此就发生了内存泄露,只有当线程被回收之后,本线程泄露的内存才会被回收。
ThreadLocal中的get和set以及remove都有用来清除key为null的value值的方法,但是get和set并不会清除的太及时,因此发生内存泄露的程度会减轻
使用虚引用的会在执行get set和GC的时候删除用不到的value,但是强引用的话,只有在线程结束以及主动remove的时候才会清除,加重了内存泄露
用完及时remove掉可避免
注
强引用 new对象为强引用,强引用存在的时候,gc不会回收
软引用 softReference 如果将要发生内存溢出,回收后仍不够用的话,会对堆里软引用的对象进行回收,比弱引用略强
弱引用 只要垃圾回收,就一定会回收
虚引用 没什么卵用,比弱引用更弱
2.线程不安全
public class ThreadLocalUnsafe implements Runnable {
//此处用的静态Number对象
public static Number number = new Number(0);
//ThreadLocal实际持有的是对象的引用,因为对象是共享的,
//所以每个线程的ThreadLocal持有的是同一个对象的实例的不同副本,所以结果并不是预想中的1
//解决方案 1:去掉static ,让每个线程有自己独立的Number实例
// 2:不再单独定义number,在创建ThreadLocal的时候设置初始值,如:
/*public static ThreadLocal<Number> values = new ThreadLocal<Number>(){
@Override
protected Number initialValue() {
return new Number(0);
}
};*/
public static ThreadLocal<Number> values = new ThreadLocal<>();
@Override
public void run() {
number.setNum(number.getNum()+1);
values.set(number);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-----"+values.get());
}
public static void main(String[] a){
for(int i=0;i<5;i++){
new Thread(new ThreadLocalUnsafe()).start();
}
}
private static class Number{
private int num;
public Number(int num){
this.num = num;
}
public int getNum(){
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Number{" +
"num=" + num +
'}';
}
}
}
面试:ThreadLocal 实现