多线程下怎样保证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);

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

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

总结

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

相关文章

  • 详解feign调用session丢失解决方案

    详解feign调用session丢失解决方案

    这篇文章主要介绍了详解feign调用session丢失解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-02-02
  • SpringBoot如何使用slf4j日志及其项目配置、MVC支持

    SpringBoot如何使用slf4j日志及其项目配置、MVC支持

    这篇文章主要介绍了SpringBoot如何使用slf4j日志及其项目配置、MVC支持,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • SpringBoot父子线程数据传递的五种方案介绍

    SpringBoot父子线程数据传递的五种方案介绍

    在实际开发过程中我们需要父子之间传递一些数据,比如用户信息等。该文章从5种解决方案解决父子之间数据传递困扰,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-09-09
  • java jpa查询没有id表的方法

    java jpa查询没有id表的方法

    本文主要介绍了java jpa查询没有id表的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-09-09
  • 解决SecureRandom.getInstanceStrong()引发的线程阻塞问题

    解决SecureRandom.getInstanceStrong()引发的线程阻塞问题

    这篇文章主要介绍了解决SecureRandom.getInstanceStrong()引发的线程阻塞问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • SpringBoot自动装配原理详解

    SpringBoot自动装配原理详解

    这篇文章主要介绍了SpringBoot自动装配原理的相关资料,帮助大家更好的理解和学习使用SpringBoot框架,感兴趣的朋友可以了解下
    2021-03-03
  • spring无法引入注解及import org.springframework.web.bind.annotation.*报错的解决

    spring无法引入注解及import org.springframework.web.bind.annota

    本文主要介绍了spring无法引入注解及import org.springframework.web.bind.annotation.*报错的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Java编程将汉字转Unicode码代码示例

    Java编程将汉字转Unicode码代码示例

    偶然看到Unicode编码,觉得挺有意思,于是搜索了相关资料,准备学习学习,本文主要是一个Unicode编码的简单Java实现,需要的朋友可以了解下。
    2017-10-10
  • Java中的main方法调用非静态方法处理

    Java中的main方法调用非静态方法处理

    这篇文章主要介绍了Java中的main方法调用非静态方法处理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • 深入了解Java线程池:从设计思想到源码解读

    深入了解Java线程池:从设计思想到源码解读

    这篇文章将从设计思想到源码解读,带大家深入了解Java的线程池,文中的示例代码讲解详细,对我们的学习或工作有一定的帮助,需要的可以参考一下
    2021-12-12

最新评论