在多线程中,遇到线程安全性的问题,一般有两种解决办法:
一、使用同步控制的synchronized,将并发变成同步,是一种时间换空间的办法,有时候效率较低。
二、使用ThreadLocal线程本地存储,为各个线程分配一个变量的副本,每个线程都是操作的自己的变量,读写不会干扰到其他线程,实现了数据隔离,是一种空间换时间的办法。不过因为ThreadLocal为每个线程都分配了一份拷贝,如果不及时回收,可能会造成内存泄漏。
ThreadLocal是将线程与对象绑定在一起,每个线程操作的都是自己独立的一份拷贝,通过getter和setter对变量进行读写,所以每次都会读到线程的最新值,保证了可见性。
ThreadLocal类中的方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
get是获取变量值,并且在获取值之前一定要先set,否则就调用setInitialValue方法返回value。get方法底层的实现是基于一个ThreadLocalMap,将当前线程传递进去获取到一个ThreadLocalMap,然后通过键值对方式获取信息。如果两者都没有,会返回一个null。
set为变量赋值。每个ThreadLocal修饰的变量只有一个,set赋值过程中,后面的会覆盖前面赋过的值。
remove移除当前线程中的变量副本。
initialValue为所有线程的变量副本赋一个初始值。
public class ThreadLocalTest {
private ThreadLocal<String> threadLocalString = new ThreadLocal<String>(){
protected String initialValue() {
return Thread.currentThread().getName();
}
};
public void set(String s1,String s2){
threadLocalString.set(s1);
threadLocalString.set(s2);
}
public String get(){
return threadLocalString.get();
}
public static void main(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
new Thread(new Runnable() {
public void run() {
//test.set("11111","11!!!");
System.out.println(test.get());
}
}).start();
new Thread(new Runnable() {
public void run() {
test.set("22222","222!!!");
System.out.println(test.get());
}
}).start();
}
}
运行结果:
可以看到后面的值覆盖掉了前面的,并且将set注释掉之后,get返回的是初始化的数据,并且两个线程的变量相互不影响。
ThreadLocal最常见的用途就是在分配数据库链接中。单线程情况下,getConnection是没有问题的,但是在多线程情况下,可能一个线程还在使用connection,另一个线程就调用了close方法将链接关闭了,这样就会有安全性问题。如果使用ThreadLocal,为每个线程都分配一个connection,各个线程互不影响,就不会出现安全性问题了。
还有就是用在session管理中,每个线程有各自的session,互不影响。