多线程-ThreadLocal
什么是 Threadlocal?
为每一个线程提供一个局部变量,主内存中的数据不再进行共享。
底层实现原理:
代码示例:
package com.test.thread.threadloca;
/**
* 底层原理:
* map 集合 + 当前线程
* set 方法底层实现
* get 方法实现
*/
public class ThreadLocaDemo {
public static void main(String[] args) {
// 三个线程共享同一个数据
// ResNumber resNumber1 = new ResNumber();
//
// LocaThread t1 = new LocaThread(resNumber1);
// LocaThread t2 = new LocaThread(resNumber1);
// LocaThread t3 = new LocaThread(resNumber1);
// t1.start();
// t2.start();
// t3.start();
// // 如果现在需要三个线程不共享数据,各自使用各自的。
// // 缺点:如果有很多个对象都不需要共享数据的时候,就需要定义很多数据对象
// ResNumber resNumber1 = new ResNumber();
// ResNumber resNumber2 = new ResNumber();
// ResNumber resNumber3 = new ResNumber();
//
// LocaThread t1 = new LocaThread(resNumber1);
// LocaThread t2 = new LocaThread(resNumber2);
// LocaThread t3 = new LocaThread(resNumber3);
// t1.start();
// t2.start();
// t3.start();
// 如果现在需要三个线程不共享数据,各自使用各自的。
// 使用 ThreadLoca 就可以实现这个效果
ResNumber resNumber1 = new ResNumber();
LocaThread t1 = new LocaThread(resNumber1);
LocaThread t2 = new LocaThread(resNumber1);
LocaThread t3 = new LocaThread(resNumber1);
t1.start();
t2.start();
t3.start();
}
}
class LocaThread extends Thread{
ResNumber resNumber;
public LocaThread(){}
public LocaThread(ResNumber resNumber){
this.resNumber = resNumber;
}
@Override
public void run(){
for (int i = 0; i < 3; i++) {
System.out.println(getName()+ " , " + resNumber.getNumber());
}
}
}
class ResNumber{
public int count = 0;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 0;
};
};
public String getNumber(){
count = threadLocal.get() + 1;
threadLocal.set(count);
return count + "";
}
// public String getNumber(){
// count = count + 1;
// return count + "";
// }
}
注意事项:ThreadLocal不调用remove方法会导致业务逻辑错误
为什么会导致业务逻辑出错:
当ThreadLocal用于线程池、web应用或者线程被多次重复使用的时候,特别要注意,以web应用为例:
我们都知道web应用很多类都是单例模式,如spring默认注入方式所创建的类就是一个单例,当不同的http请求到达服务器的时候,实际上都是使用了同一个实例,假如该实例使用了全局变量,当请求A修改了这个变量,后面到来的其它请求(此时不管A请求是否结束),如请求B再使用该变量的时候,实际上是被请求A修改过的,这会导致业务逻辑出错,而且很难被发现,这种情况,通常是使用ThreadLocal来解决,因为不同的请求虽然使用了同一个实例,但所使用的线程却不同,但有一点需要特别注意,那就是web容器的线程是重复使用的,web容器使用了线程池,当一个请求使用完某个线程,该线程会放回线程池被其它请求使用,这就导致一个问题,不同的请求还是有可能会使用到同一个线程(只要请求数量大于线程数量),而ThreadLocal是属于线程的,如果我们使用完ThreadLocal对象而没有手动删掉,那么后面的请求就有机会使用到被使用过的ThreadLocal对象,如果一个请求在使用ThreadLocal的时候,是先get()来判断然后再set(),那就会有问题,因为get到的是别的请求set的内容,如果一个请求每次使用ThreadLocal,都是先set再get,那就不会有问题,因为一个线程同一时刻只被一个请求使用,只要我们每次使用之前,都设置成自己想要的内容,那就不会在使用的过程中被覆盖。使用ThreadLocal最好是每次使用完就调用remove方法,将其删掉,避免先get后set的情况导致业务的错误。
例子:
分库 有3个库 db1 db2 db3
web应用使用了线程池 假如有10个线程
request1请求到达web服务端的时候,经过分库逻辑处理后,定位到的是db1,tomcat线程池分配的线程号是thread1,在给予thread1线程的threadlocal.set 保存的数据源是db1
由于tomcat线程池的线程是复用的,request2 恰巧tomcat给予该请求分配到的也是threade1,如果此时针对request2的请求,是不走分库逻辑的,用到的是配置死的db2数据源,那么此时就会导致request2用到的是上个处理请求request1的thread1里面的threadlocal的数据源db1,此时就会导致逻辑错误,找不到相应的表的错误