多线程下怎样保证OkHttpClient的线程安全

 更新时间:2024年01月15日 08:46:50   作者:timi先生  
这篇文章主要介绍了多线程下怎样保证OkHttpClient的线程安全问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

多线程下如何保证OkHttpClient的线程安全

多线程下的线程安全是很多同学都会遇到问题之一,虽然都说在客户端使用多线程是不可取的,但客户端本身是在一个多线程的环境下时,这个问题就不得不考虑了。

目前有以下几个方面来解决这个问题

我们来看看都有什么:

  • 单例模式:将 OkHttpClient 实例设计为单例,确保所有线程共享同一个实例。这样可以避免多个线程创建多个 OkHttpClient 实例,从而提高性能和资源利用率。
  • 避免修改配置:在多线程环境中,尽量避免在运行时修改 OkHttpClient 的配置。多个线程同时修改配置可能会导致竞争条件和不一致的状态。如果需要修改配置,建议在初始化阶段完成,并在后续的使用中只读取配置。
  • 使用连接池:OkHttpClient 内部使用连接池来管理网络连接,确保连接的重用和资源的有效利用。默认情况下,OkHttpClient 会自动使用连接池。你可以通过设置连接池的参数来调整连接池的大小、保持时间等。
  • 避免共享请求体:如果多个线程使用同一个 RequestBody 对象发送请求,可能会导致不可预期的结果。每个请求应该有自己的 RequestBody 对象,以避免并发访问的问题。
  • 避免共享 Response 对象:OkHttp 的 Response 对象是非线程安全的,因此应避免多个线程共享同一个 Response 对象。每个线程应该独立处理自己的 Response 对象。
  • 使用 OkHttpClient 的新实例:如果你需要在不同的线程中独立使用 OkHttpClient,可以为每个线程创建一个新的 OkHttpClient 实例。这样可以避免线程之间的状态混乱和资源冲突。

这几个方案中单例模式的 OkHttpClient 实例是效率最高的方案之一。

因为单例模式确保所有线程共享同一个 OkHttpClient 实例,避免了多个线程创建多个实例的开销和资源浪费。

but,我们说的前提是多线程下,那么并发访问可能带来的竞争条件和同步问题是单例模式下无法避免的。

除了单例模式之外,其他方案的效率取决于具体的使用场景和需求。今天我们先来说说如何使用 OkHttpClient 的新实例来避免多线程下的线程安全。

使用 OkHttpClient 的新实例这个方案的核心在于我们为每一个新的线程都创建了OkHttpClient客户端示例,以此来避免线程共享资源和相互竞争。

为了实现这个目标,我们就需要2个至关重要的对象:

  • 1、线程唯一标识
  • 2、可以批量创造OkHttpClient的工厂

首先我们在我们的方法中可以使用以下代码来获取当前使用该方法的线程ID:

long threadId = Thread.currentThread().getId();

有了线程ID,下一步就是如何使用它。我们在使用它之前,需要建立OkHttpClient的工厂

如下:

public class OkHttpClientFactory {

    private static final ThreadLocal<ConcurrentHashMap<Long, OkHttpClient>> clientMapThreadLocal = new ThreadLocal<>();

    public OkHttpClient getInstance(long threadId) {
        ConcurrentHashMap<Long, OkHttpClient> threadMap = clientMapThreadLocal.get();
        if (threadMap == null) {
            threadMap = new ConcurrentHashMap<>();
            clientMapThreadLocal.set(threadMap);
        }
        OkHttpClient value = threadMap.computeIfAbsent(threadId, k -> new OkHttpClient().newBuilder()
                .connectTimeout(10, TimeUnit.SECONDS) // 设置连接超时时间为10秒
                .readTimeout(30, TimeUnit.SECONDS) //读取超时时间设置为30秒
                .build());
        if (threadMap.size() == 1) {
            // 如果这是唯一剩下的(threadId -> value),则删除 ThreadLocal
            clientMapThreadLocal.remove();
        }
        return value;
    }
}

我们简单的解释一下这段代码

1、clientMapThreadLocal:这是一个 ThreadLocal 对象,用于存储每个线程对应的 ConcurrentHashMap 实例。ThreadLocal 可以确保每个线程都有自己独立的 ConcurrentHashMap 实例。

2、getInstance() 方法:这是获取 OkHttpClient 实例的方法。它接受一个 threadId 参数作为线程的唯一标识,用于区分不同的线程。

3、threadMap:首先,代码从 clientMapThreadLocal 中获取当前线程的 ConcurrentHashMap 实例。如果当前线程尚未在 clientMapThreadLocal 中拥有对应的实例,它会创建一个新的 ConcurrentHashMap 并将其设置到 clientMapThreadLocal 中。

4、threadMap.computeIfAbsent():接下来,通过 computeIfAbsent() 方法,根据 threadId 获取对应的 OkHttpClient 实例。如果 threadId 在 threadMap 中不存在,则使用 new OkHttpClient().newBuilder() 创建一个新的 OkHttpClient 实例,并设置一些默认的连接和读取超时时间。

5、threadMap.size() == 1:如果 threadMap 中只剩下一个元素(即当前线程的 threadId 对应的 OkHttpClient 实例),则删除 clientMapThreadLocal 中的 threadMap。这是为了避免在没有其他线程需要使用 OkHttpClient 的情况下,保持对 threadMap 的引用。

到了这里相信有很多同学已经明白了,这个方案的核心逻辑就是想办法让每个线程都拥有自己的实例。

最后我们可以在任何方法中使用以下代码来获取安全,且支持高并发的OkHttpClient :

  long threadId = Thread.currentThread().getId();
  OkHttpClientFactory factory = new OkHttpClientFactory();
  OkHttpClient client =  factory.getInstance(threadId);

但需要注意的,这个方案并非没有缺点。

它对与计算机资源的要求相比于其它的方案要搞得多…

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java中的文件操作总结(干货)

    java中的文件操作总结(干货)

    本篇文章主要介绍了java中的文件操作总结(干货),主要有文件读写,遍历文件夹,文件夹操作等,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-02-02
  • 使用maven打包/跳过某个modules

    使用maven打包/跳过某个modules

    本文总结了在Maven项目中跳过或单独构建模块的方法,包括使用`-pl`、`-am`和`-amd`参数来选择性地执行模块构建,以及通过`-Dmaven.test.skip`跳过测试,以提高构建效率
    2024-12-12
  • MybatisPlus处理四种表与实体的映射及id自增策略分析

    MybatisPlus处理四种表与实体的映射及id自增策略分析

    在最近的工作中,碰到一个比较复杂的返回结果,发现简单映射已经解决不了这个问题了,只好去求助百度,学习mybatis表与实体的映射应该怎么写,将学习笔记结合工作碰到的问题写下本文,供自身查漏补缺,同时已被不时之需
    2022-10-10
  • 若依框架升级springBoot3启动druid-spring-boot-starter报错问题及解决

    若依框架升级springBoot3启动druid-spring-boot-starter报错问题及解决

    文章主要描述了在使用Spring Boot 3时,由于缺少`com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties`类型的bean而报错,解决方案是考虑在配置中定义该类型bean
    2026-01-01
  • SpringBoot配置shiro安全框架的实现

    SpringBoot配置shiro安全框架的实现

    这篇文章主要介绍了SpringBoot配置shiro安全框架的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Java基础语法之对象的比较方式

    Java基础语法之对象的比较方式

    文章介绍了Java中自定义类类型的比较大小的三种方式:覆写基类的equals方法、基于Comparable接口的比较、基于Comparator接口自定义比较器的比较,每种方式都有其适用场景和局限性
    2026-02-02
  • SpringBoot 枚举类型的自动转换的实现

    SpringBoot 枚举类型的自动转换的实现

    一般我们在数据库都会定义数值型的枚举常量,不管是序列化还是反序列化都是需要我们手动去转换成枚举类型的,本文主要介绍了Spring Boot 枚举类型的自动转换,感兴趣的可以了解一下
    2022-03-03
  • java中的本地缓存GuavaCache使用方法和示例教程

    java中的本地缓存GuavaCache使用方法和示例教程

    GuavaCache是Google提供的高效本地缓存工具,支持线程安全、自动加载、过期策略及内存回收,适用于高并发场景,可提升系统性能,减少数据库访问压力
    2025-07-07
  • SpringBoot的HTTPS配置实现

    SpringBoot的HTTPS配置实现

    本文主要介绍了SpringBoot的HTTPS配置实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • 什么是springMVC 视图和视图解析器实例代码

    什么是springMVC 视图和视图解析器实例代码

    在 springMVC 中的目标方法最终返回都是一个视图,返回的视图都会由一个视图解析器来处理,这篇文章主要介绍了什么是springMVC 视图和视图解析器,需要的朋友可以参考下
    2023-09-09

最新评论