对ThreadLocal的理解

一、什么是ThreadLocal

看名知意,线程本地变量 ,也就是某个线程的变量。可以理解为该线程独享的变量。

二、关于ThreadLocal的API

ThreadLocal的API
上图显示了ThreadLocal的API,分别有get()、set()、remove()、withInitial();

三、探究源码实现

探究思路:透过get()方法的探究,猜测一种实现方式,然后通过set()、remove()来验证;
源码理解
可以大概摸索地建立如下模型:
ThreadLocal模型
所以在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方法
显然,无论是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,互不干扰!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值