一、ThreadLocal和synchronized的区别
1、分析以下代码
package com.xie.threadlocal;
public class Test1 {
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
public static void main(String[] args) {
Test1 test1=new Test1();
for (int i=0;i<5;i++){
Thread thread = new Thread(() -> {
test1.setName(Thread.currentThread().getName()+"的数据");
System.out.println("==================");
System.out.println(Thread.currentThread().getName()+"--->"+test1.getName());
});
thread.setName(String.valueOf("线程的"+i));
thread.start();
}
}
}
结论:线程与线程之间会相互影响
2、如何解决上面的线程互相影响?
①使用ThreadLocal
package com.xie.threadlocal;
public class Test1 {
ThreadLocal<String> name=new ThreadLocal<>();
public void setName(String name){
this.name.set(name);
}
public String getName(){
return this.name.get();
}
public static void main(String[] args) {
Test1 test1=new Test1();
for (int i=0;i<5;i++){
Thread thread = new Thread(() -> {
test1.setName(Thread.currentThread().getName()+"的数据");
System.out.println("==================");
System.out.println(Thread.currentThread().getName()+"--->"+test1.getName());
});
thread.setName(String.valueOf("线程的"+i));
thread.start();
}
}
}
②使用synchronized
package com.xie.threadlocal;
public class Test1 {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static void main(String[] args) {
Test1 test1 = new Test1();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Test1.class) {
test1.setName(Thread.currentThread().getName() + "的数据");
System.out.println("==================");
System.out.println(Thread.currentThread().getName() + "--->" + test1.getName());
}
}
});
thread.setName(String.valueOf("线程的" + i));
thread.start();
}
}
}
1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
3、Synchronized是时间换空间;ThreadLocal是空间换时间。
二、ThreadLocal的内部结构
JDK8之前
JDK8之后,Thread和ThreadLocal位置换了
这样设计的好处:
①存储更少的Entry
②当Thread销毁的时候,ThreadLocalMap也会被销毁
三、ThreadLocal的方法(原码)
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);
}
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();
}
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;
}
remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
四、ThreadLocalMap的基本结构
五、ThreadLocal的内存泄漏问题
1、ThreadLocalMap的强依赖
我们最常用的直接new一个对象或者通过反射创建一个对象,都是强依赖;对于强依赖,有一个很关键的点,就是如果jvm内存不足的时候,就算要报异常也不会回收这一部分内存。
把应用jvm启动内存调小到1m,然后运行一下代码:
public static void main(String[] args) {
String str = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
List<String> list = new ArrayList<>();
for(int i = 0;i < 100000;i ++) {
list.add(i + str);
}
System.out.println(list.size());
}
2、ThreadLocalMap的弱依赖
弱引用相对软引用,依赖强度又弱了一些;而弱引用和软引用的区别是,每次垃圾回收都会回收掉弱引用持有的对象。
public static void main(String[] args) throws Exception {
User u = new User();
WeakReference<User> studentWeakRef = new WeakReference<>(u);
u = null;
System.out.println(studentWeakRef.get());
System.gc();
System.out.println("After Gc:");
System.out.println(studentWeakRef.get());//gc之后一定会被回收
}
3、ThreadLocalMap为什么要使用弱依赖
在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.这就意味着使用threadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏.
4、如何正确的使用ThreadLocal
①将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
②每次使用完ThreadLocal,都调用它的remove()方法,清除数据。