设为首页 加入收藏

TOP

面试再也不怕问ThreadLocal了(一)
2023-08-06 07:49:47 】 浏览:79
Tags:ThreadLocal

要解决多线程并发问题,常见的手段无非就几种。加锁,如使用synchronized,ReentrantLock,加锁可以限制资源只能被一个线程访问;CAS机制,如AtomicInterger,AtomicBoolean等原子类,通过自旋的方式来尝试修改资源;还有本次我们要介绍的ThreadLocal类,通过为每个线程维护一个变量副本,每个线程都有自己的资源了,自然没有并发问题。ThreadLocal也是一个高频面试题,看下如下的问题,是否没想象中那么简单呢,看完这篇文章以后面试再问ThreadLocal就毫无鸭梨了。

  • ThreadLocal 作用,原理
  • 你在哪些场景使用过ThreadLocal,有什么注意事项
  • ThreadLocalMap的key为什么设计为弱引用,value为什么不设置为弱引用
  • 如何将父线程的ThreadLocal传递给子线程
  • 如何将线程的ThreadLocal传递给线程池中的线程
  • ThreadLocal设计上可以做哪些优化

ThreadLocal原理

ThreadLocal设计上为每个线程维护一份线程私有数据,它可以避免多线程之间共享资源竞争问题,同时可以在线程执行的不同阶段传递变量。
关于原理主要涉及到3个类,ThreadLocal,Thread,ThreadLocalMap。
ThreadLocal本身只是个“壳”,其操作的都是它的一个内部类ThreadLocalMap,一个类似HashMap的结构,但它不实现Map接口,ThreadLocalMap内部维护了一个Entry数组,存放实际的数据,Entry的key就是ThreadLocal对象本身,value是要存放的值,每次读写数据,就是通过TheradLocal对象计算hashcode,定位到数组的下标操作。Entry是一个继承了WeakReference<ThreadLocal<?>>的类,作为key的ThreadLocal对象会被设置为弱引用。

public class ThreadLocal<T> {

    static class ThreadLocalMap {

	private Entry[] table;

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}

Thread线程类内部有一个threadLocals属性,就是该线程对应的ThreadLocalMap,这个字段是通过ThreadLocal维护,也就是操作入口都是在ThreadLocal。

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

我们看下ThreadLocal.set方法源码

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

非常好理解,拿到当前线程,拿到当前线程的ThreadLocalMap,把当前ThreadLocal作为key,和value传递给ThreadMap保存。
用一张图来表示一下三者的关系,如下:

TheradLocal的应用

有时候面试官会问你在哪些场景使用过ThreadLocal,看你到底有没有真正使用过,记住我如下例子就行啦。(以下代码都是默写的伪代码~)

spring动态数据源
有些时候需要在一次方法内操作不同数据源,这个时候就涉及到多数据源的切换。我们会定义一个AbstractRoutingDataSource用来决定选哪个数据源

 public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSource();
    }
}

选哪个数据源是通过从当前线程ThreadLocal获取

public class DynamicDataSourceHolder {

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static String getDataSource() {
        return threadLocal.get();
    }

    public static void setDataSource(String dataSource) {
        threadLocal.set(dataSource);
    }

    public static void clearDataSource() {
        threadLocal.remove();
    }

}

接着可以定义一个注解和切面,在方法执行前判断拿到这个注解标记的数据源,将值设置到ThreadLocal,并在DynamicDataSource决定使用哪个数据时获取到,实现数据源切换,伪代码如下:

public @interface DS {
    String name();
}

@Component 
public class DynamicDataSourceAspect {

    @Pointcut("@annotation(com.my.DS)")
    public void pointcut() {}

    @Before("pointcut()")
    public void doBefore(JoinPoint joinPoint) {
        DynamicDataSourceHolder.set(dataSourceName);
    }

    @After("pointcut()")
    public void after(JoinPoint point) {
        DynamicDataSourceHolder.remove(dataSourceName);
    }
}

关于动态数据有兴趣的可以看下mybatis plus的dynamic-datasource-spring-boot-starter,原理跟我们上面说的是一样的。

可靠消息的实现
我们知道数据库和mq要确保两者都成功,一种做法就是使用本地消息表,也就是数据落库的时候同时写一条待发送的消息,并且将消息id记录到ThreadLocal,在事务提交完成后,我们可以注册回调,从本地ThreadLocal拿

首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇MQ消息队列篇:三大MQ产品的必备.. 下一篇找出乱序数组第k大的数字(堆排序..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目