Java 源码解析实战 - ThreadLocal 原理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 说起CS游戏,应该是每个中二少年的年少回忆了.游戏开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为1500、0、10.设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?如果共享,那么线程之间的并发修改会导致数据不准确.

说起CS游戏,应该是每个中二少年的年少回忆了.
游戏开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为1500、0、10.

设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?
如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?
如果共享,那么线程之间的并发修改会导致数据不准确.
能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal

注意不能将其翻译为线程本地化或本地线程
英语恰当的名称应该叫作:CopyValueIntoEveryThread

示例代码

该示例中,无 set 操作,那么初始值又是如何进入每个线程成为独立拷贝的呢?
首先,虽然ThreadLocal在定义时重写了initialValue() ,但并非是在BULLET_ NUMBER_ THREADLOCAL对象加载静态变量的时候执行;
而是每个线程在ThreadLocal.get()时都会执行到;
其源码如下
ThreadLocal # get()

每个线程都有自己的ThreadLocalMap;
如果map ==null,则直接执行setInitialValue();
如果 map 已创建,就表示 Thread 类的threadLocals 属性已初始化完毕;
如果 e==null,依然会执行到setinitialValue()
setinitialValue() 的源码如下:

这是一个保护方法,CsGameByThreadLocal中初始化ThreadLocal对象时已覆写value = initialValue() ;

getMap 的源码就是提取线程对象t的ThreadLocalMap属性: t. threadLocals.

CsGameByThreadLocal第1处,使用了ThreadLocalRandom 生成单独的Random实例;

该类在JDK7中引入,它使得每个线程都可以有自己的随机数生成器;
我们要避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed而导致性能下降.

我们已经知道了ThreadLocal是每一个线程单独持有的;
因为每一个线程都有独立的变量副本,其他线程不能访问,所以不存在线程安全问题,也不会影响程序的执行性能.
ThreadLocal对象通常是由private static修饰的,因为都需要复制到本地线程,所以非static作用不大;
不过,ThreadLocal无法解决共享对象的更新问题,下面的实例将证明这点.
因为CsGameByThreadLocal中使用的是Integer 不可变对象,所以可使用相同的编码方式来操作一下可变对象看看

输出的结果是乱序不可控的,所以使用某个引用来操作共享对象时,依然需要进行线程同步
ThreadLocal和Thread的类图

ThreadLocal 有个静态内部类ThreadLocalMap,它还有一个静态内部类Entry;
在Thread中的ThreadLocalMap属性的赋值是在ThreadLocal类中的createMap.

ThreadLocal ThreadLocalMap有三组对应的方法: get()、set()和remove();
ThreadLocal中对它们只做校验和判断,最终的实现会落在ThreadLocalMap..
Entry继承自WeakReference,只有一个value成员变量,它的key是ThreadLocal对象

再从栈与堆的内存角度看看两者的关系
ThreadLocal的弱引用路线图
一个Thread有且仅有一个ThreadLocalMap对象
一个Entry对象的 key 弱引用指向一个ThreadLocal对象
一个ThreadLocalMap 对象存储多个Entry 对象
一个ThreadLocal 对象可被多个线程共享
ThreadLocal对象不持有Value,Value 由线程的Entry 对象持有.

Entry 对象源码如下

所有的Entry对象都被ThreadLocalMap类实例化对象threadLocals持有;
当线程执行完毕时,线程内的实例属性均会被垃圾回收,弱引用的ThreadLocal,即使线程正在执行,只要ThreadLocal对象引用被置成null,Entry的Key就会自动在下一次Y - GC时被垃圾回收;
而在ThreadLocal使用set()/get()时,又会自动将那些key=null的value 置为null,使value能够被GC,避免内存泄漏,现实很骨感, ThreadLocal如源码注释所述:

ThreadLocal对象通常作为私有静态变量使用,那么其生命周期至少不会随着线程结束而结束.

三个重要方法:

  • set()
    如果没有set操作的ThreadLocal, 很容易引起脏数据问题
  • get()
    始终没有get操作的ThreadLocal对象是没有意义的
  • remove()
    如果没有remove操作,则容易引起内存泄漏

如果ThreadLocal是非静态的,属于某个线程实例,那就失去了线程间共享的本质属性;
那么ThreadLocal到底有什么作用呢?
我们知道,局部变量在方法内各个代码块间进行传递,而类变量在类内方法间进行传递;
复杂的线程方法可能需要调用很多方法来实现某个功能,这时候用什么来传递线程内变量呢?
ThreadLocal,它通常用于同一个线程内,跨类、跨方法传递数据;
如果没有ThreadLocal,那么相互之间的信息传递,势必要靠返回值和参数,这样无形之中,有些类甚至有些框架会互相耦合;
通过将Thread构造方法的最后一个参数设置为true,可以把当前线程的变量继续往下传递给它创建的子线程

public Thread (ThreadGroup group, Runnable target, String name,long stackSize, boolean inheritThreadLocals) [
   this (group, target, name,  stackSize, null, inheritThreadLocals) ;
}

parent为其父线程

if (inheritThreadLocals && parent. inheritableThreadLocals != null)
      this. inheritableThreadLocals = ThreadLocal. createInheritedMap (parent. inheritableThreadLocals) ;

createlnheritedMap()其实就是调用ThreadLocalMap的私有构造方法来产生一个实例对象,把父线程中不为null的线程变量都拷贝过来

private ThreadLocalMap (ThreadLocalMap parentMap) {
    // table就是存储
    Entry[] parentTable = parentMap. table;
    int len = parentTable. length;
    setThreshold(len) ;
    table = new Entry[len];

    for (Entry e : parentTable) {
      if (e != null) {
        ThreadLocal<object> key = (ThreadLocal<object>) e.get() ;
        if (key != null) {
          object value = key. childValue(e.value) ;
          Entry c = new Entry(key, value) ;
          int h = key. threadLocalHashCode & (len - 1) ;
          while (table[h] != null)
            h = nextIndex(h, len) ;
          table[h] = C;
          size++;
        }
    }
}

很多场景下可通过ThreadLocal来透传全局上下文的;
比如用ThreadLocal来存储监控系统的某个标记位,暂且命名为traceld.
某次请求下所有的traceld都是一致的,以获得可以统一解析的日志文件;
但在实际开发过程中,发现子线程里的traceld为null,跟主线程的traceld并不一致,所以这就需要刚才说到的InheritableThreadLocal来解决父子线程之间共享线程变量的问题,使整个连接过程中的traceld一致.
示例代码如下

import org.apache.commons.lang3.StringUtils;

/**
 * @author sss
 * @date 2019/1/17
 */
public class RequestProcessTrace {

    private static final InheritableThreadLocal<FullLinkContext> FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL
            = new InheritableThreadLocal<FullLinkContext>();

    public static FullLinkContext getContext() {
        FullLinkContext fullLinkContext = FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.get();
        if (fullLinkContext == null) {
            FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.set(new FullLinkContext());
            fullLinkContext = FULL_LINK_CONTEXT_INHERITABLE_THREAD_LOCAL.get();
        }
        return fullLinkContext;
    }

    private static class FullLinkContext {
        private String traceId;

        public String getTraceId() {
            if (StringUtils.isEmpty(traceId)) {
                FrameWork.startTrace(null, "JavaEdge");
                traceId = FrameWork.getTraceId();
            }
            return traceId;
        }

        public void setTraceId(String traceId) {
            this.traceId = traceId;
        }
    }

}

使用ThreadLocalInheritableThreadLocal透传上下文时,需要注意线程间切换、异常传输时的处理,避免在传输过程中因处理不当而导致的上下文丢失.

最后,SimpleDateFormat 是非线程安全的类,定义为static,会有数据同步风险.
通过源码可以看出,SimpleDateFormat 内部有一个Calendar对象;
在日期转字符串或字符串转日期的过程中,多线程共享时很可能产生错误;
推荐使用 ThreadLocal,让每个线程单独拥有这个对象.

ThreadLocal的副作用

为了使线程安全地共享某个变量,JDK给出了ThreadLocal.
ThreadLocal的主要问题是会产生脏数据和内存泄漏;
这两个问题通常是在线程池的线程中使用ThreadLocal引发的,因为线程池有线程复用和内存常驻两是在线程池的线程中使用ThreadLocal 引发的,因为线程池有线程复用和内存常驻两个特点

1 脏数据

线程复用会产生脏数据;
由于线程池会重用 Thread 对象,与 Thread 绑定的静态属性 ThreadLoca l变量也会被重用.
如果在实现的线程run()方法中不显式调用remove()清理与线程相关的ThreadLocal信息,那么若下一个线程不调用set(),就可能get() 到重用的线程信息;
包括ThreadLocal所关联的线程对象的value值.

脏读问题其实十分常见.
比如,用户A下单后没有看到订单记录,而用户B却看到了用户A的订单记录.
通过排查发现是由于 session 优化引发.
在原来的请求过程中,用户每次请求Server,都需要通过 sessionId 去缓存里查询用户的session信息,这样无疑增加了一次调用.
因此,工程师决定采用某框架来缓存每个用户对应的SecurityContext, 它封装了session 相关信息.
优化后虽然会为每个用户新建一个 session 相关的上下文,但由于Threadlocal没有在线程处理结束时及时remove();
在高并发场景下,线程池中的线程可能会读取到上一个线程缓存的用户信息.

  • 示例代码

输出结果
## 2 内存泄漏
在源码注释中提示使用static关键字来修饰ThreadLocal.
在此场景下,寄希望于ThreadLocal对象失去引用后,触发弱引用机制来回收EntryValue就不现实了.
在上例中,如果不进行remove(),那么当该线程执行完成后,通过ThreadLocal对象持有的String对象是不会被释放的.

  • **以上两个问题的解决办法很简单
    每次用完ThreadLocal时,及时调用remove()清理**

=========================== 增添内容 ==============================

线程封闭

避免并发异常最简单的方法就是线程封闭
即 把对象封装到一个线程里,只有该线程能看到此对象;
那么该对象就算非线程安全,也不会出现任何并发安全问题.

使用ThreadLocal是实现线程封闭的最佳实践.
ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,Map的值就是我们要封闭的对象.
每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭.

What is ThreadLocal

该类提供了线程局部 (thread-local) 变量;
这些变量不同于它们的普通对应物,因为访问某变量(通过其 get /set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本.

ThreadLocal 实例通常是类中的 private static 字段,希望将状态与某一个线程(e.g. 用户 ID 或事务 ID)相关联.

一个以ThreadLocal对象为键、任意对象为值的存储结构;
有点像HashMap,可以保存"key : value"键值对,但一个ThreadLocal只能保存一个键值对,各个线程的数据互不干扰.
该结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值.

ThreadLocal<String> localName = new ThreadLocal();
localName.set("JavaEdge");
String name = localName.get();

在线程A中初始化了一个ThreadLocal对象localName,并set了一个值JavaEdge;
同时在线程A中通过get可拿到之前设置的值;
但是如果在线程B中,拿到的将是一个null.

因为ThreadLocal保证了各个线程的数据互不干扰
看看set(T value)和get()方法的源码
返回当前线程该线程局部变量副本中的值
设置此线程局部变量的当前线程的副本到指定的值,大多数的子类都不需要重写此方法

Thread#threadLocals

可见,每个线程中都有一个ThreadLocalMap

  • 执行set时,其值是保存在当前线程的threadLocals变量
  • 执行get时,从当前线程的threadLocals变量获取

所以在线程A中set的值,是线程B永远得不到的
即使在线程B中重新set,也不会影响A中的值;
保证了线程之间不会相互干扰.

追寻本质 - 结构

从名字上看猜它类似HashMap,但在ThreadLocal中,并无实现Map接口

  • ThreadLoalMap中,也是初始化一个大小为16的Entry数组
  • Entry节点对象用来保存每一个key-value键值对

这里的key 恒为 ThreadLocal;
通过ThreadLocalset(),把ThreadLocal对象自身当做key,放进ThreadLoalMap

ThreadLoalMapEntry继承WeakReference
和HashMap很不同,Entry中没有next字段,所以不存在链表情形.

hash冲突

无链表,那发生hash冲突时何解?

先看看ThreadLoalMap插入一个 key/value 的实现

  • 每个ThreadLocal对象都有一个hash值 - threadLocalHashCode
  • 每初始化一个ThreadLocal对象,hash值就增加一个固定大小

在插入过程中,根据ThreadLocal对象的hash值,定位至table中的位置i.
过程如下

  • 若当前位置为空,就初始化一个Entry对象置于i;
  • 位置i已有对象

    • 若该Entry对象的key正是将设置的key,覆盖其value(和HashMap 处理相同);
    • 若和即将设置的key 无关,则寻找下一个空位

如此,在get时,也会根据ThreadLocal对象的hash值,定位到table中的位置.然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置.

可见,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为

内存泄露

ThreadLocal可能导致内存泄漏,为什么?
先看看Entry的实现:

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

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

通过之前的分析已经知道,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

避免内存泄露

既然发现有内存泄露的隐患,自然有应对策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

    ThreadLocal<String> localName = new ThreadLocal();
    try {
        localName.set("JavaEdge");
        // 其它业务逻辑
    } finally {
        localName.remove();
    }

题外小话

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的.
一般情况下,通过set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的;
各个线程中访问的是不同的对象.

**另外,说ThreadLocal使得各线程能够保持各自独立的一个对象;
并不是通过set()实现的,而是通过每个线程中的new 对象的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。**
通过set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map;
执行get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自线程中的对象.
ThreadLocal实例是作为map的key来使用的.

如果set()进去的东西本来就是多个线程共享的同一个对象;
那么多个线程的get()取得的还是这个共享对象本身,还是有并发访问问题。

Hibernate中典型的 ThreadLocal 应用

private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

首先判断当前线程中有没有放入 session,如果还没有,那么通过sessionFactory().openSession()来创建一个session;
再将session set()到线程中,实际是放到当前线程的ThreadLocalMap;
这时,对于该 session 的唯一引用就是当前线程中的那个ThreadLocalMap;
threadSession 作为这个值的key,要取得这个 session 可以通过threadSession.get();
里面执行的操作实际是先取得当前线程中的ThreadLocalMap;
然后将threadSession作为key将对应的值取出.
这个 session 相当于线程的私有变量,而不是public的.

显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了.

如果不用ThreadLocal怎么实现呢?

可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的;
或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法.
但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了

总之,ThreadLocal不是用来解决对象共享访问问题的;
而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式

  • 每个线程中都有一个自己的ThreadLocalMap类对象;
    可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象.
  • 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦.

当然如果要把本来线程共享的对象通过set()放到线程中也可以,可以实现避免参数传递的访问方式;
但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决;
但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中

ThreadLocal的应用场合

我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

可以看到ThreadLocal类中的变量只有这3个int型:

private final int threadLocalHashCode = nextHashCode();  
private static AtomicInteger nextHashCode =
        new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647; 

而作为ThreadLocal实例的变量只有 threadLocalHashCode
nextHashCodeHASH_INCREMENT 是ThreadLocal类的静态变量
实际上

  • HASH_INCREMENT是一个常量,表示了连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量
  • nextHashCode 表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值

看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作,构造方法ThreadLocal()里什么操作都没有,唯一的操作是这句

private final int threadLocalHashCode = nextHashCode();  

那么nextHashCode()做了什么呢

private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

就是将ThreadLocal类的下一个hashCode值即nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。.

因此ThreadLocal实例的变量只有这个threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例;
ThreadLocal类主要是作为工具类来使用,那么set()进去的对象是放在哪儿的呢?

看一下上面的set()方法,两句合并一下成为

ThreadLocalMap map = Thread.currentThread().threadLocals;  

这个ThreadLocalMap 类是ThreadLocal中定义的内部类,但是它的实例却用在Thread类中:

public class Thread implements Runnable {  
    ......  
  
    /* ThreadLocal values pertaining to this thread. This map is maintained 
     * by the ThreadLocal class. */  
    ThreadLocal.ThreadLocalMap threadLocals = null;    
    ......  
} 

再看这句:

if (map != null)  
    map.set(this, value);  

也就是将该ThreadLocal实例作为key,要保持的对象作为值,设置到当前线程的ThreadLocalMap 中,get()方法同样看了代码也就明白了.

参考

《码出高效:Java开发手册》

目录
相关文章
|
5天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
51 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
14天前
|
物联网 调度 vr&ar
鸿蒙HarmonyOS应用开发 |鸿蒙技术分享HarmonyOS Next 深度解析:分布式能力与跨设备协作实战
鸿蒙技术分享:HarmonyOS Next 深度解析 随着万物互联时代的到来,华为发布的 HarmonyOS Next 在技术架构和生态体验上实现了重大升级。本文从技术架构、生态优势和开发实践三方面深入探讨其特点,并通过跨设备笔记应用实战案例,展示其强大的分布式能力和多设备协作功能。核心亮点包括新一代微内核架构、统一开发语言 ArkTS 和多模态交互支持。开发者可借助 DevEco Studio 4.0 快速上手,体验高效、灵活的开发过程。 239个字符
182 13
鸿蒙HarmonyOS应用开发 |鸿蒙技术分享HarmonyOS Next 深度解析:分布式能力与跨设备协作实战
|
13天前
|
自然语言处理 搜索推荐 数据安全/隐私保护
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
鸿蒙登录页面设计展示了 HarmonyOS 5.0(Next)的未来美学理念,结合科技与艺术,为用户带来视觉盛宴。该页面使用 ArkTS 开发,支持个性化定制和无缝智能设备连接。代码解析涵盖了声明式 UI、状态管理、事件处理及路由导航等关键概念,帮助开发者快速上手 HarmonyOS 应用开发。通过这段代码,开发者可以了解如何构建交互式界面并实现跨设备协同工作,推动智能生态的发展。
107 10
鸿蒙登录页面好看的样式设计-HarmonyOS应用开发实战与ArkTS代码解析【HarmonyOS 5.0(Next)】
|
12天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
4天前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
35 14
|
9天前
|
安全 API 数据安全/隐私保护
速卖通AliExpress商品详情API接口深度解析与实战应用
速卖通(AliExpress)作为全球化电商的重要平台,提供了丰富的商品资源和便捷的购物体验。为了提升用户体验和优化商品管理,速卖通开放了API接口,其中商品详情API尤为关键。本文介绍如何获取API密钥、调用商品详情API接口,并处理API响应数据,帮助开发者和商家高效利用这些工具。通过合理规划API调用策略和确保合法合规使用,开发者可以更好地获取商品信息,优化管理和营销策略。
|
10天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
12天前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
60 1
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
11天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

热门文章

最新文章

推荐镜像

更多