多线程下怎样保证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并发编程之同步容器,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好的帮助,需要的朋友可以参考下
    2021-05-05
  • 一篇文章搞定数据库连接池

    一篇文章搞定数据库连接池

    数据库连接池在编写应用服务是经常需要用到的模块,太过频繁的连接数据库对服务性能来讲是一个瓶颈,使用缓冲池技术可以来消除这个瓶颈,本文就来介绍Java常见的几种,感兴趣的可以了解一下
    2021-07-07
  • 简单了解springboot eureka交流机制

    简单了解springboot eureka交流机制

    这篇文章主要介绍了简单了解springboot eureka交流机制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • struts2.5+框架使用通配符与动态方法常见问题小结

    struts2.5+框架使用通配符与动态方法常见问题小结

    这篇文章主要介绍了struts2.5+框架使用通配符与动态方法常见问题 ,在文中给大家提到了Struts2.5框架使用通配符指定方法 ,需要的朋友可以参考下
    2018-09-09
  • Java插入排序算法实现方法例子

    Java插入排序算法实现方法例子

    所谓排序,是将一组数据按照特定顺序重新排列的过程,稳定排序算法中相同键值的元素排序后保持原有顺序,直接插入排序和希尔排序是插入排序的两种形式,这篇文章主要介绍了Java插入排序算法实现的相关资料,需要的朋友可以参考下
    2024-10-10
  • 解决JavaEE开发中字符编码出现乱码的问题

    解决JavaEE开发中字符编码出现乱码的问题

    下面小编就为大家带来一篇解决JavaEE开发中字符编码出现乱码的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • Java代理模式详细解析

    Java代理模式详细解析

    这篇文章主要为大家详细介绍了Java代理模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-03-03
  • Java Web学习之Cookie和Session的深入理解

    Java Web学习之Cookie和Session的深入理解

    这篇文章主要给大家介绍了关于Java Web学习之Cookie和Session的相关资料,需要的朋友可以参考下
    2018-04-04
  • Springboot服务实现执行SQL脚本文件

    Springboot服务实现执行SQL脚本文件

    这篇文章主要介绍了Springboot服务实现执行SQL脚本文件方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • 浅谈maven的jar包和war包区别 以及打包方法

    浅谈maven的jar包和war包区别 以及打包方法

    下面小编就为大家分享一篇浅谈maven的jar包和war包区别 以及打包方法,具有很好的参考价值,希望对大家有所帮助
    2017-11-11

最新评论