1.˫?双端双端???Դ??
2.Python 爬虫进阶篇——diskcahce缓存(二)
3.OkHttp3源码详解之 okhttp连接池复用机制(一)
4.一文详解 ArrayDeque 双端队列使用及实现原理
5.LinkedBlockingQueue
˫????Դ??
fork/join框架在Java并发包中扮演着重要角色,尤其在Java 8的无后无后并行流中。本文将深入剖析其设计思路、源码源码核心角色和实现机制。双端双端
首先,无后无后fork/join的源码源码流星蝴蝶剑源码工作原理是将大任务分解成小任务,并利用多核处理。双端双端其特殊之处在于运用了work-stealing算法,无后无后通过双端队列分配任务,源码源码即使线程处理完一个任务,双端双端也能从其他未完成的无后无后任务中“窃取”以提高效率。
核心角色包括ForkJoinPool,源码源码作为任务的双端双端管理者和线程容器,负责任务的无后无后提交和workerThread的管理。ForkJoinWorkerThread则是源码源码实际执行任务的“工人”,处理队列中的任务,并通过work-stealing机制优化资源利用。WorkQueue是存放任务的双端队列,ForkJoinTask则定义了任务类型,分为有返回值和无返回值两种。
在初始化阶段,ForkJoinPool通过ForkJoinWorkerThreadFactory创建线程,任务的提交逻辑分为首次提交和任务切分后提交。首次提交会确保队列的创建和加锁,任务切分则在workerThread中进行。任务的消费则由workerThread或非workerThread线程根据任务状态进行处理。
至于任务的mtkx20源码窃取,工作线程在run()方法中通过scan(WorkQueue, int r)函数实现,不断尝试从队列中“窃取”任务,直到找到或者遍历完所有队列。
尽管文章只是概述,深入研究fork/join的源码是理解其内在机制的关键,这将有助于在实际开发中更有效地利用并发框架。
Python 爬虫进阶篇——diskcahce缓存(二)
上一篇文章为大家介绍了diskcache的基础用法,本文将继续深入探讨diskcache的更多高级功能。
关于diskcache,它是一种基于SQLite数据库的缓存对象管理方式。SQLite是一个轻量级的基于磁盘的数据库,它不需要单独的服务器进程,并支持SQL查询。在上篇文章的源码截图上,你可以看到一些SQL语句的使用。
diskcache支持使用diskcache.FanoutCache自动分片基础数据库。分片是对数据进行水平分区,可以减少写入时的阻塞。尽管读和写不会互相阻碍,但写入会阻碍其他写入。分片的默认值为8。
以下是一个示例代码:
# 示例代码
在示例中,我们在diskcache_2文件夹中创建了一个具有四个分片和一秒超时的缓存。如果操作耗时超过一秒,它们将尝试中止操作。
那么每一个分片的cookies注入 php源码大小是多少呢?分片的大小都是平均分配的,占总空间的四分之一。diskcache的默认大小为1GB。
diskcache提供了一个collections.deque兼容的双端队列diskcache.Deque。双端队列是堆栈和队列的一般化,可以在前后都可以进行快速访问和编辑,且可持久化,操作比较便捷。
以下是一个示例代码:
# 示例代码
运行结果如下:
Popleft 获取队列最前面的一个元素,pop获取末尾的一个元素;
Appendleft 在队列最开始添加一个元素,append 在末尾添加一个元素;
Extendleft 在队列最开始添加一个数组,extend在末尾添加一个数组;
那么deque的存储位置在哪里?可以使用以下命令查看队列的存储位置:
包括cache也是一个可以使用directory属性查看默认存储的位置;
那么如何指定directory呢?Deque的第二个参数指定存储位置,第一个参数为队列的初始值。或者可以直接指定参数名称,如下:
Index 类似于dict(字典),可持久化,使用也比较便捷。以下是一个示例:
# 示例代码
popitem表示获取最后一个键对值,并且删除,结果如下:
peekitem() 可传递last 参数是否获取最后一个,不会删除原始值;
setdefault(key,default) 可以给指定的key值设置默认值,在查找时可以预先设置一个默认值,防止key值不存在而抛出异常。
keys()与values()可以查找所有Index中的key值与value值,然而没有提供判断key值是否存在的方法,但是可以使用setdefault的方法自行封装。
Lock还记得上篇文章中提到的帝国带采集源码Lock锁吗?先看一下源码:
可以看到这里的锁用的就是cache的add方法的特性,源码中也给出了使用的方式:
从源码上看有一个while 死循环,直到add成功时才会被释放,这里处理的方式不是很好,可能会造成死锁,所以一般情况下,都会添加一个过期的时间,如下:
RLock还有一种RLock锁,与Lock锁的区别是RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。以下是一个示例:
结果是执行成功。
看一下源码:
从源码中可以看出,判断锁的条件是os.getpid()(进程pid)与threading.get_ident()(线程标识符),如果每次acquire时的pid与ident都相同的时,即可成功。那么就可以在相同的进程中无限次数的acquire,但是多少次acquire就得多少次的release,防止死锁。
那么是使用Lock还是RLock呢?这个要具体的看实际情况,并不是谁就一定好。
总结本次推文中介绍了diskcache中FanoutCache缓存分片、双端队列deque、Index、Lock以及RLock。
大家可以在实际中灵活运用,diskcache缓存的优势还是很大的,无需安装其他的模块,并且在文件管理器中能直接查看,还可以利用缓存的像素小鸟java源码一些特性使用多线程的去实现业务等。
但是也是有缺点的,即受制于本地文件系统的限制。每个磁盘每个目录下的文件数量是有限制的,所以需要结合实际情况使用。
OkHttp3源码详解之 okhttp连接池复用机制(一)
提高网络性能优化,关键在于降低延迟和提升响应速度。
在浏览器中发起请求时,header部分通常如下所示:
keep-alive是指浏览器与服务端之间保持长连接,这种连接可以复用。在HTTP1.1中,它默认是开启的。
连接复用为何能提高性能?通常,在发起http请求时,我们需要完成TCP的三次握手、传输数据,最后释放连接。三次握手的过程可以参考这里:TCP三次握手详解及释放连接过程。
一次响应的过程:
在高并发的请求连接情况下或同一客户端多次频繁的请求操作中,无限制地创建连接会导致性能低下。
如果使用keep-alive,在timeout空闲时间内,连接不会关闭,相同的重复请求将复用原有的connection,减少握手的次数,大幅提高效率。
并非keep-alive的timeout设置时间越长,性能就越好。长时间不关闭会导致过多的僵尸连接和泄露连接出现。
那么,OkHttp3在客户端是如何实现类似keep-alive的机制的?
连接池的类位于okhttp3.ConnectionPool。我们的目标是了解如何在timeout时间内复用connection,并有效地对其进行回收清理操作。
其成员变量代码片段:
excutor:线程池,用于检测闲置socket并进行清理。
connections:connection缓存池。Deque是一个双端列表,支持在头尾插入元素,这里用作LIFO(后进先出)堆栈,多用于缓存数据。
routeDatabase:用于记录连接失败的router。
2.1 缓存操作:
ConnectionPool提供对Deque进行操作的方法,包括put、get、connectionBecameIdle、evictAll等操作,分别对应放入连接、获取连接、移除连接、移除所有连接操作。
2.2 连接池的清理和回收:
在观察ConnectionPool的成员变量时,我们了解到一个Executor线程池用于清理闲置的连接。注释中这样解释:
Background threads are used to cleanup expired connections
我们在put新连接到队列时,会先执行清理闲置连接的线程。调用的正是executor.execute(cleanupRunnable);方法。观察cleanupRunnable:
线程中不停调用Cleanup清理的动作并立即返回下次清理的间隔时间。继而进入wait等待之后释放锁,继续执行下一次的清理。所以可能理解成它是个监测时间并释放连接的后台线程。
了解cleanup动作的过程。这里就是如何清理所谓闲置连接的流程。怎么找到闲置的连接是主要解决的问题。
在遍历缓存列表的过程中,使用连接数目inUseConnectionCount和闲置连接数目idleConnectionCount的计数累加值都是通过pruneAndGetAllocationCount()是否大于0来控制的。那么很显然,pruneAndGetAllocationCount()方法就是用来识别对应连接是否闲置的。>0则不闲置,否则就是闲置的连接。
进入观察:
好了,原先存放在RealConnection中的allocations派上用场了。遍历StreamAllocation弱引用链表,移除为空的引用,遍历结束后返回链表中弱引用的数量。所以可以看出List>就是一个记录connection活跃情况的List。>0表示活跃,=0表示空闲。StreamAllocation在列表中的数量就是物理socket被引用的次数。
解释:StreamAllocation被高层反复执行aquire与release。这两个函数在执行过程中其实是在一直在改变Connection中的List大小。
搞定了查找闲置的connection操作,我们回到cleanup的操作。计算了inUseConnectionCount和idleConnectionCount之后,程序又根据闲置时间对connection进行了一个选择排序,选择排序的核心是:
通过对比最大闲置时间选择排序可以方便地查找出闲置时间最长的一个connection。如此一来,我们就可以移除这个没用的connection了!
总结:清理闲置连接的核心主要是引用计数器List>和选择排序算法以及excutor的清理线程池。
一文详解 ArrayDeque 双端队列使用及实现原理
在探索Okhttp源码的奥秘时,一个不可或缺的组件便是ArrayDeque,一种强大的双端队列,它在数据进出两端提供了高效的操作。ArrayDeque作为Queue的扩展,拥有如offerFirst、offerLast、addFirst和addLast等一系列方法,允许在队列的两端进行元素的添加和移除,甚至可以设置为限制性操作,比如只允许一端操作。它的核心实现是基于数组,其中包含了head和tail这两个关键索引,它们控制着元素的进出。
让我们深入剖析ArrayDeque的内部构造和关键接口:
双端操作的魔法ArrayDeque的队列操作如诗如画,addFirst和offerFirst在队列前端插入,如E1、E2,而addLast和offerLast则在队列尾部,如Ea、Eb。head标识当前队首位置,tail则指向下一个待添加的位置,这种设计使得队列的增删操作既灵活又高效。
初始容量与动态扩容ArrayDeque的构造器提供了多种选项,包括默认的8元素数组和自定义长度。默认构造会生成一个元素的数组,而自定义版本则通过allocateElements()函数找到大于所需长度的最小2的幂,确保足够的存储空间。例如,如果输入值是2^n,它会被提升到2^(n+1),而大于2^的值则设为2^,确保数组长度始终是2的幂次。
首部操作的源码揭秘在核心操作中,offerFirst和addFirst的执行策略至关重要。offerFirst在数组末尾添加元素,若必要,会触发doubleCapacity()方法进行扩容。addFirst则避免了空指针问题,先在末尾添加,空间不足时才扩容。
删除与出队pollFirst和removeFirst方法负责移除队首元素,遇到空队列时会抛出异常或返回null。同样,pollLast和removeLast用于移除队尾,同样具有类似的处理机制。
尾部操作与数组扩容offerLast和addLast操作在数组前端向后添加,当队列满时,也会触发doubleCapacity()进行扩容,以保持性能。ArrayDeque的灵活性体现在不仅支持入队(offerLast)和出队(pollFirst)操作,类似地,push入堆栈和pop出堆栈也通过相同的逻辑进行。
总的来说,ArrayDeque凭借其独特的设计和高效的实现,为Okhttp等应用提供了强大的数据管理能力。深入理解其工作原理,无疑有助于我们在编写高效代码时游刃有余。如果你对ArrayDeque的更多细节感兴趣,不妨参考官方文档或深入研究其在实际项目中的应用,如在Okhttp中的妙用。
LinkedBlockingQueue
LinkedBlockingDequeå¨ç»æä¸æå«äºä¹å讲解è¿çé»å¡éåï¼å®ä¸æ¯Queueèæ¯Dequeï¼ä¸æç¿»è¯æå端éåï¼å端éåæå¯ä»¥ä»ä»»æä¸ç«¯å ¥éæè åºéå ç´ çéåï¼å®ç°äºå¨éå头åéåå°¾çé«ææå ¥å移é¤LinkedBlockingDequeæ¯é¾è¡¨å®ç°ç线ç¨å®å ¨çæ ççåæ¶æ¯æFIFOãLIFOçå端é»å¡éåï¼å¯ä»¥å顾ä¸ä¹åçLinkedBlockingQueueé»å¡éåç¹ç¹ï¼æ¬è´¨ä¸æ¯ç±»ä¼¼çï¼ä½æ¯åæäºä¸åï¼
QueueåDequeçå ³ç³»æç¹ç±»ä¼¼äºåé¾è¡¨åååé¾è¡¨ï¼LinkedBlockingQueueåLinkedBlockingDequeçå é¨ç»ç¹å®ç°å°±æ¯åé¾è¡¨åååé¾è¡¨çåºå«ï¼å ·ä½å¯åèæºç ã
å¨ç¬¬äºç¹ä¸å¯è½æäºäººæäºçé®ï¼ä¸¤ä¸ªäºæ¥éåä¸ä¸ªäºæ¥éçåºå«å¨åªéï¼æ们å¯ä»¥èè以ä¸åºæ¯ï¼
A线ç¨å è¿è¡å ¥éæä½ï¼B线ç¨éåè¿è¡åºéæä½ï¼å¦ææ¯LinkedBlockingQueueï¼A线ç¨å ¥éè¿ç¨è¿æªç»æï¼å·²è·å¾éè¿æªéæ¾ï¼ï¼B线ç¨åºéæä½ä¸ä¼è¢«é»å¡çå¾ ï¼éä¸åï¼ï¼å¦ææ¯LinkedBlockingDequeåB线ç¨ä¼è¢«é»å¡çå¾ ï¼åä¸æéï¼A线ç¨å®ææä½æ继ç»æ§è¡
LinkedBlockingQueueä¸è¬çæä½æ¯è·åä¸æéå°±å¯ä»¥ï¼ä½æäºæä½ä¾å¦removeæä½ï¼åéè¦åæ¶è·å两æéï¼ä¹åçLinkedBlockingQueue讲解æ¾ç»è¯´æè¿
LinkedBlockingQueue ç±äºæ¯åé¾è¡¨ç»æï¼åªè½ä¸ç«¯æä½ï¼è¯»åªè½å¨å¤´ï¼ååªè½å¨å°¾ï¼å æ¤ä¸¤æéæçæ´é«ãLinkedBlockingDeque ç±äºæ¯åé¾è¡¨ç»æï¼ä¸¤ç«¯å¤´å°¾é½è½è¯»åï¼å æ¤åªè½ç¨ä¸æéä¿è¯ååæ§ã å½ç¶æçä¹å°±æ´ä½
ArrayBlockingQueue
LinkedBlockingQueue
é®é¢ï¼ä¸ºä»ä¹ArrayBlockingQueue ä¸è½ç¨ä¸¤æé
å 为ååºåï¼ArrayBlockingQueue çå ç´ éè¦åå移å¨ã
LinkedBlockingQueueå é¨ç±åé¾è¡¨å®ç°ï¼åªè½ä»headåå ç´ ï¼ä»tailæ·»å å ç´ ãæ·»å å ç´ åè·åå ç´ é½æç¬ç«çéï¼ä¹å°±æ¯è¯´LinkedBlockingQueueæ¯è¯»åå离çï¼è¯»åæä½å¯ä»¥å¹¶è¡æ§è¡ãLinkedBlockingQueueéç¨å¯éå ¥é(ReentrantLock)æ¥ä¿è¯å¨å¹¶åæ åµä¸ç线ç¨å®å ¨ã
LinkedBlockingQueueä¸å ±æä¸ä¸ªæé å¨ï¼åå«æ¯æ åæé å¨ãå¯ä»¥æå®å®¹éçæé å¨ãå¯ä»¥ç©¿å ¥ä¸ä¸ªå®¹å¨çæé å¨ãå¦æå¨å建å®ä¾çæ¶åè°ç¨çæ¯æ åæé å¨ï¼LinkedBlockingQueueçé»è®¤å®¹éæ¯Integer.MAX_VALUEï¼è¿æ ·åå¾å¯è½ä¼å¯¼è´éåè¿æ²¡æ满ï¼ä½æ¯å åå´å·²ç»æ»¡äºçæ åµï¼å å溢åºï¼ã
size()æ¹æ³ä¼éåæ´ä¸ªéåï¼æ¶é´å¤æ度为O(n),æ以æ好éç¨isEmtpy
1.å¤æå ç´ æ¯å¦ä¸ºnullï¼ä¸ºnullæåºå¼å¸¸
2.å é(å¯ä¸æé)
3.å¤æéåé¿åº¦æ¯å¦å°è¾¾å®¹éï¼å¦æå°è¾¾ä¸ç´çå¾
4.å¦æ没æé满ï¼enqueue()å¨éå°¾å å ¥å ç´
5.éåé¿åº¦å 1ï¼æ¤æ¶å¦æéåè¿æ²¡æ满ï¼è°ç¨signalå¤éå ¶ä»å µå¡éå
1.å é(ä¾æ§æ¯ReentrantLock)ï¼æ³¨æè¿éçéååå ¥æ¯ä¸åç两æé
2.å¤æéåæ¯å¦ä¸ºç©ºï¼å¦æ为空就ä¸ç´çå¾
3.éè¿dequeueæ¹æ³åå¾æ°æ®
3.åèµ°å ç´ åéåæ¯å¦ä¸ºç©ºï¼å¦æä¸ä¸ºç©ºå¤éå ¶ä»çå¾ ä¸çéå
åçï¼å¨éå°¾æå ¥ä¸ä¸ªå ç´ ï¼ å¦æéå没满ï¼ç«å³è¿åtrueï¼ å¦æéå满äºï¼ç«å³è¿åfalseã
åçï¼å¦æ没æå ç´ ï¼ç´æ¥è¿ånullï¼å¦ææå ç´ ï¼åºé
1ãå ·ä½å ¥éä¸åºéçåçå¾ï¼
å¾ä¸æ¯ä¸ä¸ªèç¹ååé¨å表示å°è£ çæ°æ®xï¼åè¾¹ç表示æåçä¸ä¸ä¸ªå¼ç¨ã
1.1ãåå§å
åå§åä¹åï¼åå§åä¸ä¸ªæ°æ®ä¸ºnullï¼ä¸headålastèç¹é½æ¯è¿ä¸ªèç¹ã
1.2ãå ¥é两个å ç´ è¿å
1.3ãåºéä¸ä¸ªå ç´ å
表é¢ä¸çï¼åªæ¯å°å¤´èç¹çnextæéæåäºè¦å é¤çx1.nextï¼äºå®ä¸è¿æ ·æè§çå°±å®å ¨å¯ä»¥ï¼ä½æ¯jdkå®é ä¸æ¯å°åæ¥çheadèç¹å é¤äºï¼èä¸è¾¹çå°çè¿ä¸ªheadèç¹ï¼æ£æ¯åååºéçx1èç¹ï¼åªæ¯å ¶å¼è¢«ç½®ç©ºäºã
2ãä¸ç§å ¥é对æ¯ï¼
3ãä¸ç§åºé对æ¯ï¼