问题引入:在多线程环境下,如何保证自己的变量不被其他线程篡改?
Spring如何处理Bean在多线程下的线程安全问题?
先看一个线程不安全的例子:
根据不考虑多线程的代码语义可知,我们期望静态变量经每个线程修改后变成该线程的编号并打印。
1 public class ThreadLocalDemo {
2 private static int num=0;
3 public static void main(String[] args) {
4 Thread[] threads=new Thread[5];
5 for(int i=0;i<threads.length;i++){
6 final int count=i;
7 threads[i]=new Thread(()->{
8 num+=count;
9 System.out.println(Thread.currentThread().getName()+" :"+num);
10 });
11 }
12 for(int i=0;i<threads.length;i++){
13 threads[i].start();
14 }
15 }
16 }
然而由于多线程环境,实际结果是这样的:
解决线程安全问题有两大思路:(1)共享,加锁 (2)不共享
ThreadLocal本地线程变量就是在合适的情形下采用第二种路线来保证线程安全,即将变量私有不共享。使用方法如下:
1 public class ThreadLocalDemo {
2 private static int num=0;
3 private static ThreadLocal<Integer> threadLocal1=new ThreadLocal<Integer>(){
4 @Override //ThreadLocal类源码中提供的初始化方法。
5 protected Integer initialValue() {
6 return 0;
7 }
8 };
9
10 public static void main(String[] args) {
11 Thread[] threads = new Thread[5];
12 for (int i = 0; i < threads.length; i++) {
13 final int count = i;
14 threads[i] = new Thread(() -> {
15 int x = threadLocal1.get();
16 x += count;
17 threadLocal1.set(x);
18 System.out.println(Thread.currentThread().getName() + " :" + threadLocal1.get());
19 });
20 }
21 for (int i = 0; i < threads.length; i++) {
22 threads[i].start();
23 }
24 }
25 }
运行结果:
那么,ThreadLocal的底层原理是什么呢?ThreadLocal在使用中主要用到两个方法,set()和get(),我们不妨进入set()和get()方法一探究竟。
可见,Thread类中有一个ThreadLocal.ThreadLocalMap类型的变量threadlocals。ThreadLocalMap类是ThreadLocal类中的静态内部类,其中维护了一个Entry类型的数组。Entry类又是ThreadLocalMap类中弱引用的内部类。set()方法赋值时先拿到Thread中的ThreadLocalMap类型变量threadlocals,判断是否为空,如果为空,创建ThreadLocalMap对象赋值给Thread。如果不为空,直接修改value值。get()方法也基本同理。这种设计方式真正意义上实现了线程局部变量的意味。ThreadLocalMap类中维护了Entry类型的数组,表明了一个线程中可以有多个本地线程变量。
上面大图将get()和Set()方法和几种关键的类指示的非常清晰,看图很容易捋清思路。画了图突然不想写字了。 --_--
Spring处理Bean在多线程下的线程安全问题就是使用了ThreadLocal本地线程的方法,比如在Service层加事务要求,就要保证每一个Dao层操作的数据库链接必须是同一个(不同的链接就不能保证事务),怎样保证几个操作拿到的数据库链接是同一个呢?就是将数据库连接使用ThreadLocal保存到线程本地变量中。