多线程-ThreadLocal

多线程-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,此时就会导致逻辑错误,找不到相应的表的错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值