对ThreadLocal的理解
一、什么是ThreadLocal
看名知意,线程本地变量 ,也就是某个线程的变量。可以理解为该线程独享的变量。
二、关于ThreadLocal的API
上图显示了ThreadLocal的API,分别有get()、set()、remove()、withInitial();
三、探究源码实现
探究思路:透过get()方法的探究,猜测一种实现方式,然后通过set()、remove()来验证;
可以大概摸索地建立如下模型:
所以在Thread内部定义的ThreadLocal可以在线程内部取到对应的值。
四、发现问题
那么问题来了,这样是如何做到ThreadLocal数据隔离的?
因为ThreadLocal定义在线程内部,绑定了对应的线程。
如果还不理解,那么回忆一下我们是如何创建线程的:
比较常用的创建线程方法:
①Clazz extends Thread
上面所说的map正是存在于Thread里;
②Clazz implement Runnable
实现了Runnable接口,但是线程还是得这样创建 Thread t = new Thread(new Clazz);本质上与上无疑,只是增加了灵活性。
加上取map取当前线程的map(这样解析吧,线程的成员变量,每次new的thead都将不一样…),所以说,map仅仅是线程的成员变量,明显地各个线程的成员变量之间不存在数据共享!
五、补充知识
这里补充一点关于线程是如何实现数据共享的,在创建线程的时候,Clazz的成员变量i(基本类型)和object(引用类型)就已经赋值了,那么初始化的线程都有独立的工作内存(来自主存的副本),要是我修改引用类型,是不是直接利用地址来修改?那就达到了共享变量的目的。而对于基本类型,由于每一个字段的字面量都是处于线程的栈中的实际的值,那么修改它们是不是不会影响到其他线程的值,这样就达到了不共享变量的目的。
六、验证 五
可以做如下实验验证一遍:
①定义一个类,包含了三个成员变量,一个先赋值的引用、一个run时赋值的引用、一个基本类型;
②在运行时分别进行修改;
③输出成员变量的值;
④如果先赋值的引用全部线程都修改了,run时赋值的引用当前线程改变了而其他线程的引用不变、当前线程机基本类型发生了变化而其他线程不变,这就说明证明了上面的猜想。
public class TestObject {
private int value;
public TestObject(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
public String toString() {
return "TestObject{" +
"value=" + value + " " +
this.hashCode()+'}';
}
}
public class ThreadSharingTest implements Runnable{
private int i;
private TestObject testObjectInit;
private TestObject testObject;
@Override
public String toString() {
return "ThreadSharingTest{" +
"i=" + i +
", testObjectInit=" + testObjectInit +
", testObject=" + testObject +
'}';
}
public ThreadSharingTest(TestObject testObjectInit,int i) {
this.testObjectInit = testObjectInit;
this.i = i;
}
@Override
public void run() {
synchronized (this){
i = (int) Thread.currentThread().getId();
testObject = new TestObject(i);
testObjectInit.setValue((int)(Math.random()*1000));
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this);
}
public static void main(String[] args) {
TestObject testObject = new TestObject(0);
new Thread(new ThreadSharingTest(testObject,0)).start();
new Thread(new ThreadSharingTest(testObject,0)).start();
}
}
实验结果:
可以看出i的值发生了变化、testObjectInit没有变化(且hashCode相同)说明为同一个对象、testObject发生了变化且不为同一个对象!
七、阶段总结
目前来说,模型已经建立完毕,接下来将对set()、remove()进行验证,如果验证成功,则模型成立,否则,模型证明为错的。
八、从源码中探究
显然,无论是set还是remove方法都是对线程的成员变量map进行修改!
九、总结
可能证明过程不够严谨,尤其是 八 ,但也能说明部分事实——ThreadLocal通过对线程内部维护的map实现。
十、应用场景
数据库的连接:
一、一般情况:
public class ThreadLocalTest implements Runnable{
private static Connection connection;
public Connection getConnection(){
return connection;
}
public void setConnection(){
try {
connection = DriverManager.getConnection("...", "root", "");
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void run() {
if (connection==null){
setConnection();
}
Connection connection = getConnection();
//do something else...
}
public static void main(String[] args) {
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
}
}
很明显了,这种情况会发生并发问题,就是可能创建出多个connection,每次获取到的connection可能不一致的问题。
二、那我就加锁呗:
public class ThreadLocalTest implements Runnable{
private static Connection connection;
public synchronized Connection getConnection(){
return connection;
}
public synchronized void setConnection(){
try {
connection = DriverManager.getConnection("...", "root", "");
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void run() {
if (connection==null){
setConnection();
}
Connection connection = getConnection();
//do something else...
}
public static void main(String[] args) {
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
}
}
这样解决了,这里虽然可行,但每次只能使用一个connection,并且不能实现高并发,因为每次只能有一个线程可以获取连接,这难免会影响性能。
三、使用ThreadLocal
public class ThreadLocalTest implements Runnable{
private static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
public synchronized Connection getConnection(){
return connectionThreadLocal.get();
}
public synchronized void setConnection(){
try {
connectionThreadLocal.set(DriverManager.getConnection("...", "root", ""));
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void run() {
if (connectionThreadLocal.get()==null){
setConnection();
}
Connection connection = getConnection();
//do something else...
}
public static void main(String[] args) {
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
new Thread(new ThreadLocalTest()).start();
}
}
这样,每一个线程都将有一个独立的connection,互不干扰!