ThreadLocal类的作用,原理和应用场景

2019年7月9日 9点热度 0条评论 来源: IT乾坤

如果想看更多Java多线程技术的话,可以点击下面的链接查看哦

https://www.itqiankun.com/article/1564891332

ThreadLocal类的作用

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改,这就是ThreadLocal类的作用

什么意思呢,我们看下面的代码,我们看到结果应该是萧一和萧二和萧三增加之后的年龄应该都是增加2,但是结果呢,萧二是增加了2,然后萧一增加了4,然后萧三增加了6,这就有问题了

package com.third;
public class MainTest { 
    public static void main(String[] args) { 
        User bank = new User();
        Thread first = new Thread(() -> bank.add(2), "萧一");
        Thread second = new Thread(() -> bank.add(2), "萧二");
        Thread third = new Thread(() -> bank.add(2), "萧三");
        first.start();
        second.start();
        third.start();
    }
}

class User { 
    private int age = 5;
    public void add(int num) { 
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "当前年龄是:" + this.age);
        this.age += num;
        System.out.println(threadName + "增加" + num + " 年龄之后,现在年龄:" + this.age);
        try { 
            Thread.sleep(1000);
        } catch (InterruptedException e) { 
            e.printStackTrace();
        }
    }
}
  萧一当前年龄是:5
  萧二当前年龄是:5
  萧二增加2 年龄之后,现在年龄:7
  萧三当前年龄是:7
  萧三增加2 年龄之后,现在年龄:11
  萧一增加2 年龄之后,现在年龄:9

我们可以通过ThreadLocal来解决上面的问题,此时要注意增加每一个线程里面的年龄的时候,不要使用下面注释掉的//age+=num,这个是不对的

package com.third;
import java.util.function.Supplier;
public class MainTest { 
    public static void main(String[] args) { 
        User bank = new User();
        Thread first = new Thread(() -> bank.add(2), "萧一");
        Thread second = new Thread(() -> bank.add(2), "萧二");
        Thread third = new Thread(() -> bank.add(2), "萧三");
        first.start();
        second.start();
        third.start();
    }
}

class User { 
    ThreadLocal<Integer> age = ThreadLocal.withInitial(new Supplier<Integer>() { 
        @Override
        public Integer get() { 
            return 5;
        }
    });
    public void add(int num) { 
       String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "当前年龄是:" + age.get());
        age.set(age.get()+num); //age+=num;
        System.out.println(threadName + "增加" + num + " 年龄之后,现在年龄:" + age.get());
        try { 
            Thread.sleep(1000);
        } catch (InterruptedException e) { 
            e.printStackTrace();
        }
    }
}

结果如下所示

  萧一当前年龄是:5
  萧一增加2 年龄之后,现在年龄:7
  萧二当前年龄是:5
  萧二增加2 年龄之后,现在年龄:7
  萧三当前年龄是:5
  萧三增加2 年龄之后,现在年龄:7

ThreadLocal类的原理

ThreadLocal的实现原理就是ThreadLocal里面有一个静态的ThreadLocalMap,这个是怎么操作的呢,往下看

ThreadLocalMap是ThreadLocal里面的一个静态类
源码如下所示

ThreadLocalMap是Thread里面的一个属性

为什么这样说呢,首先我们看到Thread类里面有一个属性就叫做ThreadLocalMap

然后我们看一下ThreadLocal里面的set()方法,看到源码可以看到当当前线程的ThreadLocalMap是null的时候,此时就会走下面的红色框里面的代码

然后就是会走下面的方法,然后new一个ThreadLocalMap赋值给当前线程的threadLocals这个变量,threadLocals这个变量在Thread里面就是ThreadLocal.ThreadLocalMap类型的

然后当set()的时候,会把当前实例化对象的ThreadLocal作为key值,然后把set()里面的参数当成value放到这个ThreadLocalMap里面

在ThreadLocal里面源码set(),此时可以看到set()的源码就是首先获取当前线程,然后利用当前线程获取一个ThreadLocalMap的对象,然后如果ThreadLocalMap对象为空,则把当前ThreadLocal当成key,set()里面的参数当成value放到ThreadLocalMap集合里面,否则创建这个ThreadLocalMap对象,然后把当前ThreadLocal当成key,set()里面的参数当成value放到ThreadLocalMap集合里面

public void set(T value) { 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

上面的createMap(t,value)源码如下

void createMap(Thread t, T firstValue) { 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

看到上面的代码之后,我们也能知道为什么ThreadLocal可以把创建的变量只能被当前线程访问,其他线程则无法访问和修改因为ThreadLocal的值是放入了当前线程的一个ThreadLocalMap实例中,所以只能在本线程中访问,其他线程无法访问。

ThreadLocal类的应用场景

承载一些线程相关的数据
因为javaee三层架构里面,如果使用事务,要在service层里面进行开启事务(因为处理逻辑业务都是service层),然后又因为如果我们要使用事务,那么就必须保证执行sql语句的connection连接和开启事务的connection连接都要保持是同一个对象,所以我们要确保在service层和dao层的两个connection连接都是同一个,但是怎么保证connection连接对象都是同一个呢
一个是通过方法传参的方式进行数据的一层一层的传递,但是这样不好,因为你把架构分成三层的目的就是为了数据的处理和逻辑业务的处理分离开来(就是dao层处理数据,service层处理业务),connection连接对象我们应该是在service层出现的,但是你却放到了dao层,这样数据的处理和逻辑业务的处理没有分离开来,javaee的三层开发就没有他的效果了,所以这一种方式的解决方法不好,所以我们就通过ThreadLocal的方式来存储这个connection对象,这样就能够保证在service层和dao层的数据保证一致了

能看到这里的同学,就帮忙点个赞吧,Thanks(・ω・)ノ

    原文作者:IT乾坤
    原文地址: https://blog.csdn.net/weixin_43689480/article/details/95175594
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。