ThradLocal原理解析及SpringSecurity无法在子线程中获取上下文信息解决

Spring Security 6 系列之三 - Filter过滤器 关于Spring Security的底层原理大概讲这么多。Spring Security的过滤器有那么多,我们这里主要讲的是几个经常使用到的,其它的过滤器后续遇到需要自定义配置的时候,我们再讲解。Spring Security默认配置显然不能符合一个真正的业务需求,那么下一章我们就要开始做各种自定义配置。 阅读详情

ThreadLocal使用及其原理解析

一、前言


项目中使用到了SpringSecurity框架作为安全验证,但是却发现一个问题,即当在子线程中获取SecurityContextHolder中存储的对象时会报空指针异常,后来发现原来SpringSecurity默认是将对象信息存储在ThreadLocal类型的变量中,因此在子线程调用便会出现空指针异常,异常代码如下:

    public void insertFill(MetaObject metaObject) {
        log.info("创建时的自动插入策略生效");
        this.strictInsertFill(metaObject, SystemConfig.createTime, Date.class, new Date());
        if(loginUser == null){
            try {
                //此处会出现空指针异常
                loginUser = (AdminLoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            }catch (Exception e){
                log.info("用户尚未登录,自动插入策略无需生效");
            }
        }
        if (loginUser != null && loginUser.getAdminUser() != null){
            this.strictInsertFill(metaObject, SystemConfig.createBy, String.class, loginUser.getAdminUser().getId());
        }
    }

SpringSecurity使用ThreadLocal存储上下文信息代码如下:

	public static SecurityContext getContext() {
		return strategy.getContext();
	}
	
 	//默认采用就是ThreadLocal进行存储
	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

	public SecurityContext getContext() {
		SecurityContext ctx = contextHolder.get();

		if (ctx == null) {
			ctx = createEmptyContext();
			contextHolder.set(ctx);
		}

		return ctx;
	}

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

因此,为了解决这一问题,打算研究一下ThreadLocal使用及其原理

二、作用


1、基本概念

ThreadLocal可以解释成线程的局部变量,也就是说一个ThreadLocal的变量只有当前自身线程可以访问,别的线程都访问不了,那么自然就避免了线程竞争。因此,ThreadLocal提供了一种与众不同的线程安全方式,它不是在发生线程冲突时想办法解决冲突,而是彻底的避免了冲突的发生

2、基本使用

创建一个ThreadLocal对象:

    public static void main(String[] args) {
        ThreadLocal<String> localVar = new ThreadLocal<>();
        localVar.set("hello world!!!"); //设置值为hello world!!!
        String val = localVar.get(); //在当前线程取出对应的值
        System.out.println(val); //输出结果为hello world!!!
        
        //新开线程取ThreadLocal变量的值
        Thread thread = new Thread(() -> {
            System.out.println(localVar.get());//输出结果为null
        });
        thread.start();
    }

由于ThreadLocal里设置的值,只有当前线程自己看得见,这意味着你不可能通过其他线程为它初始化值。为了弥补这一点,ThreadLocal提供了一个withInitial()方法统一初始化所有线程的ThreadLocal的值,此时的值对所有线程可见:

        ThreadLocal<String> localVar = ThreadLocal.withInitial(() -> "hello world!!!");
        localVar.set("hello world!!!");
        String val = localVar.get();
        System.out.println(val); //输出结果为hello world!!!
        Thread thread = new Thread(() -> {
            System.out.println(localVar.get()); //输出结果也为hello world!!!
        });
        thread.start();

三、ThradLocal的实现原理


1、get()方法

ThreadLocal变量只在单个线程内可见,那它是如何做到的呢?我们先从最基本的get()方法说起:

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程绑定的ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //ThreadLocalMap的key就是当前ThreadLocal对象实例
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //从map里取出值
                T result = (T)e.value;
                return result;
            }
        }
        //当map为空或者对应的key不存在时进行初始化
        return setInitialValue();
    }

    private T setInitialValue() {
        //直接赋值null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //当map存在时,将null与对应的ThreadLocal对象实例进行绑定
            map.set(this, value);
        else
            //当map不存在时,为当前线程创建对应的ThreadLocalMap变量
            createMap(t, value);
        return value;
    }

    protected T initialValue() {
        return null;
    }

    void createMap(Thread t, T firstValue) {
        //为当前线程创建对应的ThreadLocalMap变量--threadLocals,所有的ThreadLocal对象实例及其绑定的对象存在threadLocals中
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //ThreadLocalMap底层与HashMap有相似之处,也是通过数组的形式进行存储,每个数组中存的是内部类Entry
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

因此ThreadLocal数据隔离的实现是因为ThreadLocal类操作的是Thread的成员变量threadLocals。每个线程Thread都有自己的threadLocals,从而互相不影响。

threadLocals这个成员变量的本质又是ThreadLocalMap类,它是ThreadLocal的内部类,下面我们研究一下这个内部类的数据结构:

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

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //初始化容量
    private static final int INITIAL_CAPACITY = 16;
    //散列表
    private Entry[] table;
    //有效数量
    private int size = 0;
    //负载因子
    private int threshold;
    
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    //构造器
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
}

ThreadLocalMap底层与HashMap有相似之处,也是通过数组的形式进行存储,每个数组中存的是内部类Entry,而且Entry继承了WeakReference类,即ThreadLocalMap的key是弱引用,value是强引用

将key设为弱引用的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal对象,避免可能的内存泄露(注意:Entry中的value依然是强引用)

因此其数据结构如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PFv3JSFJ-1665133197543)(E:\JavaResources\学习笔记\ThreadLocal数据结构.png)]

2、set()方法

ThreadLocalMap和java.util.HashMap两者对于Hash冲突的解决方式是不同的。

对于HashMap而言,其使用的是链表法来处理冲突:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvJgtIVe-1665133197543)(E:\JavaResources\学习笔记\链表法.png)]

而对于ThreadLocalMap而言,其使用的是简单的线性探测法,如果发生了元素冲突,那么就使用下一个槽位存放:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMh7jXoj-1665133197544)(E:\JavaResources\学习笔记\image-20221005163649149.png)]

因此,ThreadLocal的set()过程如下:

        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            //根据hash值找到key对应的索引位置
            int i = key.threadLocalHashCode & (len-1);
            
            //判断是否发生hash冲突,如果冲突,则一直往下找,直到找到未冲突的索引位置
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //如果key一样,将值进行替换
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果key为null,表示原来的key已经被回收了,那么进行清理,具体清理过程见下文
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //解决了hash冲突,将entry放到对应的位置
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

3、ThreadLocal中的内存泄漏问题

虽然ThreadLocalMap中的key是弱引用,当不存在外部强引用的时候,就会自动被回收,但是Entry中的value依然是强引用。这个value的引用链条如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FFzh8swb-1665133197544)(E:\JavaResources\学习笔记\引用链条.png)]

可以看到,只有当Thread被回收时,这个value才有被回收的机会,否则,只要线程不退出,value总是会存在一个强引用。但是,要求每个Thread都会退出,是一个极其苛刻的要求,对于线程池来说,大部分线程会一直存在在系统的整个生命周期内,那样的话,就会造成value对象出现泄漏的可能。处理的方法是,在ThreadLocalMap进行set(),get(),remove()的时候,都会进行清理

以get()方法举例,从上文中可以看到get()方法中会调用 ThreadLocalMap.Entry e = map.getEntry(this);,如下所示:

        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                //检测到key为null,进行清理
                return getEntryAfterMiss(key, i, e);
        }

        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    //清理key为null的元素
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }


        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    //将其value置为null,从而让gc进行自动回收
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

真正用来回收value的是expungeStaleEntry()方法,在remove()和set()方法中,都会直接或者间接调用到这个方法进行value的清理。

从这里可以看到,ThreadLocal为了避免内存泄露,也算是花了一番大心思。不仅使用了弱引用维护key,还会在每个操作上检查key是否被回收,进而再回收value。

但是从中也可以看到,ThreadLocal并不能100%保证不发生内存泄漏。

比如,很不幸的,你的get()方法总是访问固定几个一直存在的ThreadLocal,那么清理动作就不会执行,如果你没有机会调用set()和remove(),那么这个内存泄漏依然会发生。

因此,一个良好的习惯依然是:当你不需要这个ThreadLocal变量时,主动调用remove(),这样对整个系统是有好处的

四、InheritableThreadLocal的作用


1、基本使用

让我们回到本文开头,本文的主要目的除了探讨ThreadLocal外,还需要解决SpringSecurity中无法在子线程中获取在主线程中存储的对象,而SpringSecurity中是将对象存在了ThradlLocal中。即实际开发过程中,我们可能会遇到这么一种场景。主线程开了一个子线程,但是我们希望在子线程中可以访问主线程中的ThreadLocal对象,也就是说有些数据需要进行父子线程间的传递

此时便可以使用InheritableThreadLocal,顾名思义,这就是一个支持线程间父子继承的ThreadLocal

    public static void main(String[] args) {
        ThreadLocal<String> localVar = new InheritableThreadLocal<>();
        localVar.set("hello world!!!"); 
        String val = localVar.get();
        System.out.println(val);//输出为hello world!!!
        Thread thread = new Thread(() -> {
            System.out.println(localVar.get());//输出也为hello world!!!
        });
        thread.start();
    }

从而每个线程都可以访问到从父进程传递过来的一个数据。虽然InheritableThreadLocal看起来挺方便的,但是依然要注意以下几点:

  • 变量的传递是发生在线程创建的时候,如果不是新建线程,而是用了线程池里的线程,就不灵了

  • 如果采用的是线程池执行异步任务,并且还想在SpringSecurity中传播上下文,推荐的方法是改写线程池的配置

        @Bean(name = "asyncPoolTaskExecutor")
        public ThreadPoolTaskExecutor executor(){
            ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
            taskExecutor.setCorePoolSize(8);
            taskExecutor.setMaxPoolSize(16);
            taskExecutor.setQueueCapacity(50);
            taskExecutor.setKeepAliveSeconds(200);
            taskExecutor.setThreadNamePrefix("async-");
            taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
            //主要是添加DelegatingSecurityContextRunnable,然后进行初始化initialize
            taskExecutor.setTaskDecorator(DelegatingSecurityContextRunnable::new);
            taskExecutor.initialize();
            return taskExecutor;
        }
    
    //参考链接:https://stackoverflow.com/questions/3467918/how-to-set-up-spring-security-securitycontextholder-strategy
    
  • 变量的赋值就是从主线程的map复制到子线程,它们的value是同一个对象,如果这个对象本身不是线程安全的,那么就会有线程安全问题

2、实现原理

InheritableThreadLocal 实际上是 ThreadLocal 的子类,我们来看下 InheritableThreadLocal 的定义:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

从源码中可以看出,InheritableThreadLocal与ThreadLocal主要的不同在于,getMap 方法的返回值变成了 inheritableThreadLocals 对象,但是在createMap 方法中,构建出来的 inheritableThreadLocals 还依然是 ThreadLocalMap 的对象。和 ThreadLocal 相比,主要是保存数据的对象从 threadLocals 变为 inheritableThreadLocals。

这样的变化,对于前面的我们所说的 ThreadLocal 中的 get/set 并不影响,也就是 ThreadLocal 的特性依然不变

于是继续深挖,发现当使用了InheritableThreadLocal之后,最大的变化发生在Thread的init方法中,init方法会在new Thread()时自动调用,源码如下:

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    //inheritThreadLocals在new Thread()时默认是true
    ...
    //找到父线程
    Thread parent = currentThread();
    ...
    ...
    //在创建子线程的时候,如果父线程存在 inheritableThreadLocals 变量且不为空,就调用 ThreadLocal.createInheritedMap 方法为子线程的 inheritableThreadLocals 变量赋值
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ...
    ...
}

//将父线程的 inheritableThreadLocals 变量值赋值给子线程的 inheritableThreadLocals 变量。因此,在子线程中就可以访问到父线程 ThreadLocal 中的数据了
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
}

因此通过在Thread的init方法中将父线程的inheritableThreadLocals赋值给子线程的inheritableThreadLocals 变量。因此,在子线程中就可以访问到父线程ThreadLocal中的数据了

五、SpringSecurity中解决子线程无法访问主线程中所存储的数据

实际上 SpringSecurity中的SecurityContextHolder一共定义了三种存储策略进行上下文的存储,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KkjsUFq-1665133197544)(E:\JavaResources\学习笔记\SecurityContextHolder的存储策略.png)]

public class SecurityContextHolder {
 public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
 public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
 public static final String MODE_GLOBAL = "MODE_GLOBAL";
    ...
    ...
}

而第二种存储策略MODE_INHERITABLETHREADLOCAL就支持在子线程中获取当前登录用户信息,而MODE_INHERITABLETHREADLOCAL 的底层使用的就是 InheritableThreadLocal,因此只需自定义SpringSecurity的上下文存储策略,便可以在子线程中获取到当前存储信息

可以通过改写系统启动参数完成该项配置,在系统启动时加上如下参数:

-Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL
激活上下文无效:解决当前执行线程中不活动的错误 激活上下文无效:解决当前执行线程中不活动的错误在编程中,经常会遇到激活上下文无效的错误。这个错误通常出现在多线程编程或异步编程中,它表示当前执行线程所需的激活上下文不是活动的。本文将详细介绍这个错误的原因,并提供一些解决方法。 阅读详情

相关推荐

取证实战:当嫌疑人电脑已关机,如何利用EFDD从休眠文件提取BitLocker密钥?

本文详细介绍了如何利用Elcomsoft Forensic Disk Decryptor(EFDD)从休眠文件(hiberfil.sys)中提取BitLocker密钥的实战技术。通过分析休眠文件中的内存快照,取证人员可以突破已关机加密设备的防线,获取关键解密信息。文章涵盖了工具配置、镜像获取、密钥提取及验证等完整流程,并提供了疑难问题解决方案和法律合规建议。

weixin_29315091的博客 344

SpringBoot Spring Security 核心组件 认证流程 用户权限信息获取详细讲解

前言 Spring Security 是一个安全框架, 可以简单地认为 Spring Security 是放在用户和 Spring 应用之间的一个安全屏障, 每一个 web 请求都先要经过 Spring Security 进行 Authenticate 和 Authoration 验证 核心组件 SecurityContextHolder SecurityContextHolder它持有的是安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权等等

daTou 1200

深入解析NRF52832安全DFU Bootloader实现机制与蓝牙OTA升级流程

本文深入解析了NRF52832芯片安全DFU Bootloader的实现机制与蓝牙OTA升级流程。详细阐述了Bootloader的核心概念、双区升级策略、源码初始化流程以及蓝牙DFU服务的数据传输协议,并提供了从环境搭建、密钥生成到编译烧录、生成升级包的完整实战指南,帮助开发者构建安全可靠的无线固件更新系统。

weixin_29188043的博客 490

【已解决】Spring Security中,多线程操作导致上下文丢失

某个功能因涉及的表比较多,想改成多线程异步操作。但是在功能实现过程中,问题出来了在登入完成后项目在请求中封装了HospitalId在请求域中,然后继承项目封装好的BaseService可以在Service层直接调用this.getHospitalId()获取值当开启多线程的时候this.getHospitalId()会报错 空指针异常正常操作是但这样还是会空指针。

lushixuan12345的博客 2076

【Android14 ShellTransitions】Transition就绪:双Transaction与SurfaceControl显示时机剖析

本文深入剖析了Android14 ShellTransitions框架中Transition就绪阶段的核心机制。重点解释了为何需要“双Transaction”来精确控制SurfaceControl的显示与隐藏时机,以确保Activity切换动画的流畅性。通过分析onTransactionReady流程、窗口绘制状态机及动画目标提升策略,揭示了系统如何协调WMCore与WMShell,实现从状态收集到动画播放的无缝衔接。

ooo22的博客 529

道闸不自动落杆故障排查:从传感器原理到控制器调试全解析

在工业自动化和物联网系统中,传感器与控制器构成的闭环控制是保障设备可靠运行的核心。其基本原理是通过传感器采集环境状态,控制器依据预设逻辑做出决策并驱动执行器动作。这一技术价值在于实现了无人值守下的安全、高效自动化,广泛应用于智能交通、安防和智能楼宇等领域。以停车场道闸系统为例,其自动落杆功能高度依赖地感线圈与防砸雷达的协同。地感线圈作为触发单元,负责感知车辆接近;而防砸雷达则作为持续监测的安全单元,通过微波探测防砸区域内是否有障碍物,其信号是控制器执行落杆决策的关键依据。当系统出现车辆通过后闸杆不落的典型故

weixin_29190651的博客 312

使用线程池时,多线程之间上下文参数传递失效解决办法

问题描述 在执行业务代码时,有时候需要异步处理一些业务,在异步线程中获取当前登录的用户信息失败。因为用户信息是在过滤器或拦截器中解析出来并设置到ThreadLocal中的,子线程是无法获取到ThreadLocal中的值的。 虽然可以使用InheritableThreadLocal替代ThreadLocal,但是只能在新创建的线程中使用。对于线程池也会失效,因为线程池中的线程是由线程池创建好并可重复利用,这时InheritableThreadLocal的值传递就失去意义了。 解决办法 自定义线程池和线程池执

xzh_blog 2467

YOLOv11火焰识别:从环境搭建到GUI部署的完整工程实践

目标检测是计算机视觉的核心任务之一,它通过定位和分类图像中的物体,为机器赋予“看懂”世界的能力。其原理通常基于深度学习模型,如YOLO系列,通过卷积神经网络提取特征并预测边界框与类别。这项技术的价值在于将算法能力转化为实际生产力,广泛应用于安防监控、工业质检、自动驾驶等场景。在工程实践中,环境配置的稳定性与数据质量往往决定项目成败,而模型部署的易用性直接影响最终用户体验。本文以火焰识别这一具体应用为切入点,详细解析了如何利用YOLOv11完成从零到产品化的全链路流程,其中涉及Conda虚拟环境管理、PyTo

weixin_29483277的博客 236

SecurityContextHolder.getContext()获取不到Security上下文数据?

在使用 MODE_INHERITABLETHREADLOCAL 安全策略时,您需要小心处理多线程环境下的安全性问题,以避免出现数据竞争、死锁等问题。同时,这种模式可能会对应用程序的性能产生一定的影响,因为它需要通过序列化和反序列化来传递安全上下文信息。因此,在使用 MODE_INHERITABLETHREADLOCAL 安全策略时需要小心,确保在必要的情况下使用它,以避免性能问题。我的项目中出现这个问题是由于使用了异步线程(如下),导致Security上下文无法从主线程传递到异步线程中。

本人的技术和文笔有限,正在努力成长中,请各位朋友多多指正与包涵! 2315

基于紫光展锐平台的音频调用到hal层逻辑

基于紫光展锐平台的音频调用到hal层逻辑 1、数据流程图 AudioPolicyService和AudioFlinger的创建: 这两个服务都在audioserver中启动,文件路径: frameworks/av/media/audioserver/main_audioserver.cpp 从AudioFlinger和AudioPolicyService的创建,到output的打开,以及AudioFlinger开始往底层写入数据的具体流程如下图: #mermaid-svg-kjHL4XWwkQXN6NdA

weixin_55053963的博客 1355

2021年J题:基于STM32F103的周期信号波形识别与参数测量系统设计(立创EDA+SZPT_ZXL)

本文详细复盘了2021年全国大学生电子设计竞赛J题获奖项目,手把手教你如何基于STM32F103微控制器设计并实现周期信号波形识别与参数测量系统。文章深入剖析了硬件电路设计(包括程控放大、工作点抬升与滞回比较器)与核心软件算法(如动态采样、FFT频谱分析与波形识别逻辑),并分享了DMA传输、FFT库使用等实战要点与避坑指南,为嵌入式信号处理与电赛备赛提供了宝贵经验。

weixin_32673065的博客 230

应用Spring Security安全框架时,异步操作中安全上下文丢失问题的解决方案

问题描述 笔者最近在开发项目时遇到一个问题,项目应用Spring Security+JWT的安全框架,用户访问时前台会在Header中携带Authorization头,SS对其进行认证并将当前登录的主体信息存储到安全上下文,在当前用户访问的任何地方都可以通过安全上下文获取登录用户的信息(例如用户名、用户手机、用户id等等),但在请求中启用另外的线程执行之后操作的时候,笔者发现异步线程中的安全上下...

qq_32734365的博客 4414

Floyd (弗洛伊德)算法简述

Floyd在1962年由Robert Floyd以其当前公认的形式出版。算法作为三个嵌套for循环的现代公式首先由Peter Ingerman在1962年描述。Floyd 算法是解决图论问题的比较经典的算法,是解决给定的加权图中顶点间的最短路径的一种算法,可以正确处理有向图的最短路径问题。

慕羽★的博客 2万+

Spring Security —05—认证用户数据

以后每当有请求到来时,Spring Security 就会先从 Session 中取出用户登录数据,保存到SecurityContextHolder 中,方便在该请求的后续处理过程中使用,同时在请求结束时将 SecurityContextHolder 中的数据拿出来保存到 Session 中,然后将SecurityContextHolder 中的数据清空。的默认存储策略,这种存储策略意味着如果在具体的业务处理代码中,开启了子线程,在子线程中去获取登录用户数据,就会获取不到。

weixin_48994585的博客 1518

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

SpringSecurity Oauth2 - 06 拦截器获取用户登录信息并存储到本地线程ThreadLocal

上一讲已经我们分析了/oauth/token认证流程,明白了整个认证过程核心做了哪些事情,这一讲看一下如何配置拦截器判断用户是否登录并获取用户登录信息,同时将获取的登录信息存储到本地线程中,主要分为两点展开说明

你今天真好看呀 4499

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Spring Security权限框架入门详解与实际使用

主要功能是提供认证和授权服务。@Component@Override@Service@Overridelog.info("登录账号:{} 用户名或密码错误.", account);throw new BussinessException("登录账号:" + account + " 用户名或密码错误");log.info("登录账号:{} 已被停用.", account);

奋斗男孩的博客 696

SpringSecurity+JWT认证流程解析

楔子 本文适合:对Spring Security有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,对SpringSecurity有兴趣的也可以当作你们的入门教程,示例代码中也有很多注释。 大家在做系统的时候,一般做的第一个模块就是认证与授权模块,因为这是一个系统的入口,也是一个系统最重要最基础的一环,在认证与授权服务设计搭建好了之后,剩下的模块才得以安全访问。 市面上一般做认证授权的框架就是shiro和Spring Security,也有大部分公司选择自己研制。出于之前看过很多Spring .

q66562636的博客 1584

Threadlocal的应用

springsecurity的整个 过滤器链对于后续springmvc中的handler方法是不可见的,在security的过滤器链中解析的token信息我们有必要存入线程上下文,以方便controller层的方法去获取这些信息进行相关处理所以这里我们先自定义一个接口UserContext// 对用户进行赋值的方法 void setCurrentUserVO(MyUserVO myUserVO);// 获取变量 Object getProperty(Object var);

qq_59620850的博客 364

SpringSecurity(八)--SecurityContext安全上下文

本节我们将讨论安全上下文,我们将分析它是如何工作的、如何从其中访问数据,以及应用程序如何在具有不同的与线程有关的场景中管理它。一般来说,我们为了让后续程序能够使用验证通过人员的信息,都会使用到它,比如编写一个SecurityUtils用来获取用户信息是经常用到的,那么学习它后,你就可以使用安全上下文存储关于已验证用户的详细信息了。

学习不止境的博客 5028

SpringSecurity入门(二)

SpringSecurity入门(二)

qq_27352081的博客 1324

Spring 事务、Security、MDC... 为什么都离不开 ThreadLocal?

是一把锋利的“双刃剑”。它以一种巧妙的“空间换时间”的思路,为我们在多线程环境下隔离数据、传递上下文提供了强大的武器。然而,它与生俱来的“内存泄漏”陷阱,要求我们必须像遵守纪律的士兵一样,严格遵循 try-finally-remove的黄金法则。

郑龙飞 1201

ThreadLocal应用场景

理解 ThreadLocal 的原理和应用场景,可以帮助我们更好地设计和开发多线程应用程序,提高程序的性能、可维护性和可靠性。 但是,一定要注意 ThreadLocal 的内存泄漏问题,务必在使用完 ThreadLocal 变量后调用 remove() 方法清除数据。 特别是在线程池场景下。

专注于技术分享 2717

ThreadLocal怎么拿不到用户信息了?

想了好久,为什么userId是空,通过控制台打印的日志发现,没有token的日志输出,然后猛然想起来,拦截器没有加对应的路径,并没有对这个请求进行拦截,也就不会有token解析的这一步,然后userId是空的。,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。后端的JWT过滤器,可以解析token,拿到用户信息,同时可以使用ThreadLocal,放到这个线程中,可以方便在别的地方使用。

m0_73179389的博客 626

为什么已经有了ThreadLocal,还需要SecurityContextHolder呢?

1、SecurityContextHolder类内部可以理解成一个Thread Local集合2、它是一个静态类,可以用来访问不同线程之间的数据3、它在ThreadLocal上进行了更高级的封装。通过前面3点已经可以知道为什么要有ThreadLocal了,再提出一个问题,

MADAO的博客 853

SecurityContextHolder多线程无法获取登录信息的原因

在 Spring Security 中,我就想从子线程获取用户登录信息,怎么办? 大家知道在 Spring Security 中想要获取登录用户信息,不能在子线程中获取,只能在当前线程中获取,其中一个重要的原因就是 SecurityContextHolder 默认将用户信息保存在 ThreadLocal 中。 但是实际上 SecurityContextHolder 一共定义了三种存储策略: public class SecurityContextHolder { public static final S

杨海吉 7170

异步丢失上下文问题解决

异步操作不管是用多线程实现还是用CompletableFuture.runAsync实现,都会出现丢失上下文,导致后续业务逻辑获取不到登录人或者token信息的情况。针对不同情况使用不同解决方式: 1、管理型平台,有登录操作,使用SpringSecurity实现权限控制的,如下: //异步之前获取上下文 SecurityContext securityContext = SecurityContextHolder.getContext(); //异步发起测算 Completable

jixinping的博客 3405
上一篇: 阿里云OSS签名直传实现(前端、后端踩坑)
下一篇: AOP、ASPECT、Spring AOP、JDK动态代理、CGLib动态代理
Simpier
博客等级 码龄7年 2粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值