├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_cn.md ├── _config.yml ├── docs ├── 1.xlsx ├── 2.xlsx ├── b1-error-rate.png ├── b1-throughput.png ├── b2-error-rate.png ├── b2-throughput.png └── benchmark.png ├── pom.xml ├── release-notes ├── v2.1.2.txt ├── v2.2.0.txt └── v2.2.1.txt └── src ├── BUILD.md ├── benchmark ├── BaseWorker.java ├── Benchmark.java ├── BenchmarkCommons.java ├── BenchmarkFastObjectPool.java ├── BenchmarkFastObjectPoolDisruptor.java ├── BenchmarkFurious.java ├── BenchmarkResult.java ├── BenchmarkStormpot.java └── Start.java ├── main └── java │ └── cn │ └── danielw │ └── fop │ ├── DisruptorObjectPool.java │ ├── ObjectFactory.java │ ├── ObjectPool.java │ ├── ObjectPoolPartition.java │ ├── PoolConfig.java │ ├── PoolExhaustedException.java │ ├── PoolInvalidObjectException.java │ └── Poolable.java └── test └── java ├── TestObjectPool.java └── cn └── danielw └── fop └── PoolConfigTest.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: maven 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | types: [opened, synchronize, reopened] 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: 17 20 | distribution: microsoft 21 | - name: Cache SonarCloud packages 22 | uses: actions/cache@v3 23 | with: 24 | path: ~/.sonar/cache 25 | key: ${{ runner.os }}-sonar 26 | restore-keys: ${{ runner.os }}-sonar 27 | - name: Cache Maven packages 28 | uses: actions/cache@v3 29 | with: 30 | path: ~/.m2 31 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 32 | restore-keys: ${{ runner.os }}-m2 33 | - name: Build and analyze 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any 36 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 37 | run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .metadata 2 | .project 3 | .classpath 4 | .settings 5 | .idea 6 | *.class 7 | *humbs.db 8 | .DS_Store 9 | .pydevproject 10 | target 11 | logs 12 | log 13 | *.log 14 | .cache 15 | out 16 | bin 17 | gen 18 | *.iml 19 | *.ipr 20 | *.iws 21 | gen-java 22 | 23 | # Numerous always-ignore extensions 24 | *.diff 25 | *.err 26 | *.orig 27 | *.log 28 | *.rej 29 | *.swo 30 | *.swp 31 | *.zip 32 | *.vi 33 | *~ 34 | *.sass-cache 35 | 36 | # OS or Editor folders 37 | .DS_Store 38 | ._* 39 | Thumbs.db 40 | .cache 41 | .tmproj 42 | *.esproj 43 | nbproject 44 | *.sublime-project 45 | *.sublime-workspace 46 | build 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/DanielYWoo/fast-object-pool/actions/workflows/build.yml/badge.svg)](https://github.com/DanielYWoo/fast-object-pool/actions/workflows/build.yml) 2 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=DanielYWoo_fast-object-pool&metric=alert_status)](https://sonarcloud.io/dashboard?id=DanielYWoo_fast-object-pool) 3 | [中文文档](/README_cn.md) 4 | 5 | fast-object-pool 6 | ================ 7 | FOP, a lightweight high performance object pool optimized for concurrent accesses, you can use it to pool expensive and non-thread-safe objects like thrift clients etc. 8 | 9 | Github repo: [https://github.com/DanielYWoo/fast-object-pool](https://github.com/DanielYWoo/fast-object-pool) 10 | 11 | Site page: [https://danielw.cn/fast-object-pool/](https://danielw.cn/fast-object-pool/) 12 | 13 | Why yet another object pool 14 | -------------- 15 | 16 | FOP is implemented with partitions to avoid thread contention, the performance test shows it's much faster than Apache commons-pool. 17 | This project is not to replace Apache commons-pool, this project does not provide rich features like commons-pool, this project mainly aims on: 18 | 1. Zero dependency (the only optional dependency is disrutpor) 19 | 2. High throughput with many concurrent requests/threads 20 | 3. Less code so everybody can read it and understand it. 21 | 22 | Configuration 23 | ------------- 24 | First of all you need to create a FOP config: 25 | 26 | 27 | ```java 28 | PoolConfig config = new PoolConfig(); 29 | config.setPartitionSize(5); 30 | config.setMaxSize(10); 31 | config.setMinSize(5); 32 | config.setMaxIdleMilliseconds(60 * 1000 * 5); 33 | ``` 34 | 35 | The code above means the pool will have at least 5x5=25 objects, at most 5x10=50 objects, if an object has not been used over 5 minutes it could be removed. 36 | 37 | Then define how objects will be created and destroyed with ObjectFactory 38 | 39 | 40 | ```java 41 | ObjectFactory factory = new ObjectFactory<>() { 42 | @Override public StringBuilder create() { 43 | return new StringBuilder(); // create your object here 44 | } 45 | @Override public void destroy(StringBuilder o) { 46 | // clean up and release resources 47 | } 48 | @Override public boolean validate(StringBuilder o) { 49 | return true; // validate your object here 50 | } 51 | }; 52 | ``` 53 | 54 | 55 | Now you can create your FOP pool and just use it 56 | 57 | 58 | ```java 59 | ObjectPool pool = new ObjectPool(config, factory); 60 | try (Poolable obj = pool.borrowObject()) { 61 | obj.getObject().sendPackets(somePackets); 62 | } 63 | ``` 64 | 65 | Shut it down 66 | 67 | 68 | ```java 69 | pool.shutdown(); 70 | 71 | ``` 72 | 73 | Install 74 | --------------- 75 | To use FOP, simply add the dependency below. 76 | ``` 77 | For Maven users: 78 | 79 | cn.danielw 80 | fast-object-pool 81 | 2.2.1 82 | 83 | 84 | For Gradle users: 85 | implementation 'cn.danielw:fast-object-pool:2.2.1' 86 | ``` 87 | 88 | If you want best performance, you can optionally add disruptor to your dependency, and use DisruptorObjectPool instead of ObjectPool. (recommended) 89 | 90 | For JDK 11+, use the dependency below. 91 | ``` 92 | For Maven users: 93 | 94 | com.conversantmedia 95 | disruptor 96 | 1.2.19 97 | 98 | 99 | For Gradle users: 100 | implementation 'com.conversantmedia:disruptor:1.2.19' 101 | ``` 102 | 103 | Logging 104 | -------------- 105 | One of the design goals of FOP is zero dependency, so we use JDK logger by default. If you use slf4j, you can optionally add jul-to-slf4j to your dependency to bridge the JDK logger to slf4j. 106 | 107 | How it works 108 | -------------- 109 | The pool will create multiple partitions, in most cases a thread always access a specified partition, 110 | so the more partitions you have, the less probability you run into thread contentions. Each partition has a 111 | blocking queue to hold poolable objects; to borrow an object, the first object in the queue will be removed; 112 | returning an object will append that object to the end of the queue. The idea is from ConcurrentHashMap's segments 113 | design and BoneCP connection pool's partition design. This project started since 2013 and has been deployed to many projects without any problems. 114 | 115 | How fast it is 116 | -------------- 117 | The source contains a benchmark test, you can run it on your own machine. On my Macbook Pro (8-cores i9), it's 50 times faster than commons-pool 2.2. 118 | 119 | Test Case 1: we have a pool of 256 objects, use 50/100/400/600 threads to borrow one object each time in each thread, then return to the pool. 120 | 121 | The x-axis in the diagram below is the number of threads, you can see Stormpot provides the best throughput. FOP is closely behind Stormpot. Apache common pool is very slow, Furious is slightly faster than it. 122 | You see the throughput drops after 200 threads because there are only 256 objects, so there will be data race and timeout with more threads. 123 | ![](docs/b1-throughput.png?raw=true) 124 | When you have 600 threads contending 256 objects, Apache common pool reaches over 85% error rate (probably because returning is too slow), basically it cannot be used in high concurrency. 125 | ![](docs/b1-error-rate.png?raw=true) 126 | 127 | The diagram above if you only borrow one object at a time per thread, if you need to borrow two objects in a thread, things are getting more interesting. 128 | 129 | Test Case 2: we have a pool of 256 objects, use 50/100/400/600 threads to borrow two objects each time each thread, then return to the pool. 130 | 131 | In this case, we could have 600x2=1200 objects required concurrently but only 256 is available, so there will be contention and timeout error. 132 | FOP keeps error rate steadily but stormpot starts to see 7% error rate with 100 threads. (I cannot test stormpot with 600 threads because it's too slow) 133 | Furious seems not working because I cannot find a way to set timeout, without timeout I see circular deadlock in Furious, 134 | so I exclude Furious from the diagram below . Both FOP and Stormpot provides timeout configuration, in the test I set timeout to 10ms. 135 | FOP throws an exception, Stormpot returns null, so we can mark it as failed (show in the error rate plot). 136 | Again, Apache common pool reaches almost 80% error rate, basically not working. 137 | ![](docs/b2-error-rate.png?raw=true) 138 | 139 | The throughput of FOP is also much better than other pools. Interestingly, in this case, Apache common pool is slightly faster than Stormpot. 140 | I don't know why Stormpot degrade so fast with two borrows in one thread. If you know how to optimize Stormpot configuration for this case please let me know. 141 | ![](docs/b2-throughput.png?raw=true) 142 | 143 | So, in short, if you can ensure borrow at most one object in each thread, Stormpot is the best choice. If you cannot ensure that, use FOP which is more consistent in all scenarios. 144 | -------------------------------------------------------------------------------- /README_cn.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/DanielYWoo/fast-object-pool/actions/workflows/build.yml/badge.svg)](https://github.com/DanielYWoo/fast-object-pool/actions/workflows/build.yml) 2 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=DanielYWoo_fast-object-pool&metric=alert_status)](https://sonarcloud.io/dashboard?id=DanielYWoo_fast-object-pool) 3 | 4 | fast-object-pool 5 | ================ 6 | FOP,一个针对并发访问优化的轻量级高性能对象池,您可以使用它来池化昂贵且非线程安全的对象,例如 thrift 客户端等。 7 | 8 | Github repo: [https://github.com/DanielYWoo/fast-object-pool](https://github.com/DanielYWoo/fast-object-pool) 9 | 10 | Site page: [https://danielw.cn/fast-object-pool/](https://danielw.cn/fast-object-pool/) 11 | 12 | 为什么还要搞一个对象池 13 | -------------- 14 | 15 | FOP 使用分区实现以避免线程争用,性能测试表明它比 Apache commons-pool 快得多。 16 | 本项目不是为了替代 Apache commons-pool,本项目没有提供 commons-pool 丰富的功能,本项目主要针对: 17 | 1.零依赖(唯一可选的依赖是disrutpor) 18 | 2. 并发请求/线程多的高吞吐量 19 | 3. 更少的代码,所以每个人都可以阅读和理解它。 20 | 21 | 配置 22 | ------------- 23 | 首先,您需要创建一个 FOP 配置: 24 | ```java 25 | PoolConfig config = new PoolConfig(); 26 | config.setPartitionSize(5); 27 | config.setMaxSize(10); 28 | config.setMinSize(5); 29 | config.setMaxIdleMilliseconds(60 * 1000 * 5); 30 | ``` 31 | 32 | 上面的代码意味着池中至少有 5x5=25 个对象,最多 5x10=50 个对象,如果一个对象超过 5 分钟没有被使用,它可以被删除。 33 | 34 | 然后定义如何使用 ObjectFactory 创建和销毁对象 35 | ```java 36 | ObjectFactory factory = new ObjectFactory<>() { 37 | @Override public StringBuilder create() { 38 | return new StringBuilder(); // create your object here 39 | } 40 | @Override public void destroy(StringBuilder o) { 41 | // clean up and release resources 42 | } 43 | @Override public boolean validate(StringBuilder o) { 44 | return true; // validate your object here 45 | } 46 | }; 47 | ``` 48 | 49 | 现在您可以创建 FOP 池并使用它 50 | ```java 51 | ObjectPool pool = new ObjectPool(config, factory); 52 | try (Poolable obj = pool.borrowObject()) { 53 | obj.getObject().sendPackets(somePackets); 54 | } 55 | ``` 56 | 57 | 关闭对象池 58 | ```java 59 | pool.shutdown(); 60 | 61 | ``` 62 | 63 | 安装使用 64 | --------------- 65 | ``` 66 | Maven: 67 | 68 | cn.danielw 69 | fast-object-pool 70 | 2.2.1 71 | 72 | 73 | Gradle: 74 | implementation 'cn.danielw:fast-object-pool:2.2.1' 75 | ``` 76 | 77 | 如果您想要获得最佳性能,您可以选择将Disruptor添加到您的依赖项中,并使用 DisruptorObjectPool 而不是 ObjectPool。 78 | 79 | 80 | For JDK 11+, 使用以下Disruptor依赖. 81 | ``` 82 | Maven: 83 | 84 | com.conversantmedia 85 | disruptor 86 | 1.2.19 87 | 88 | 89 | Gradle: 90 | implementation 'com.conversantmedia:disruptor:1.2.19' 91 | ``` 92 | 93 | Logging 94 | -------------- 95 | FOP 的设计目标之一是零依赖,所以默认使用 JDK logger。如果您使用 slf4j,您可以选择将 jul-to-slf4j 添加到您的依赖项中,以将 JDK 记录器桥接到 slf4j。 96 | 97 | 工作原理 98 | -------------- 99 | FOP会创建多个分区Pool,大多数情况下一个线程总是访问一个指定的分区,因此,您拥有的分区越多,遇到线程争用的可能性就越小。 100 | 每个分区都有一个阻塞队列以保存可池对象;借用一个对象,队列中的第一个对象将被移除;返回一个对象会将该对象附加到队列的末尾。 101 | 这个想法来自ConcurrentHashMap的segments设计和 BoneCP 连接池的分区设计。该项目从 2013 年开始,已经部署到许多项目中,没有任何问题。 102 | 103 | How fast it is 104 | -------------- 105 | 源代码中包含一个基准测试,您可以在自己的机器上运行它。 在我的 Macbook Pro(8 核 i9)上,它比 commons-pool 2.2 快 50 倍。 106 | 107 | **测试用例1**:我们有一个256个对象的池,使用50/100/400/600个线程在每个线程中每次借用一个对象,然后返回池中。 108 | 109 | 下图中的 x 轴是线程数,可以看到 Stormpot 提供了最好的吞吐量。 FOP 紧随 Stormpot 之后。 Apache common pool 很慢,Furious 比它快一点。 110 | 您会看到 200 个线程后吞吐量下降,因为只有 256 个对象,因此会有更多线程的数据竞争和超时。 111 | ![](docs/b1-throughput.png?raw=true) 112 | 当你有 600 个线程竞争 256 个对象时,Apache common pool 的错误率达到了 85% 以上(可能是返回太慢了),基本上不能用于高并发。 113 | ![](docs/b1-error-rate.png?raw=true) 114 | 115 | 上图如果每个线程一次只借一个对象,如果你需要在一个线程中借两个对象,事情就变得更有趣了。 116 | 117 | **测试用例 2**:我们有一个 256 个对象的池,使用 50/100/400/600 个线程,每次每个线程借用两个对象,然后返回池中。 118 | 119 | 在这种情况下,我们可以同时需要 600x2=1200 个对象,但只有 256 个可用,因此会出现数据竞争和超时错误。 120 | FOP 保持稳定的错误率,但 Stormpot 在 100 个线程时开始看到 7% 的错误率。 (我不能用 600 个线程测试stormpot,因为它太慢了) 121 | Furious 似乎无法工作,因为我找不到设置超时的方法,没有超时控制导致 Furious 循环死锁, 122 | 所以我从下图中排除了 Furious。 FOP 和 Stormpot 都提供了超时配置,在测试中我将超时设置为 10ms。 123 | FOP 抛出异常,Stormpot 返回 null,因此我们可以将其标记为失败(显示在错误率图中)。 124 | 同样,Apache common pool 几乎达到了 80% 的错误率,基本上无法正常工作。 125 | ![](docs/b2-error-rate.png?raw=true) 126 | 127 | FOP 的吞吐量也比其他池好很多。有趣的是,在这种情况下,Apache common pool 比 Stormpot 稍快。 128 | 我不知道为什么 Stormpot 在一个线程中有两次借用时降级如此之快。如果您知道如何针对这种情况优化 Stormpot 配置,请告诉我。 129 | ![](docs/b2-throughput.png?raw=true) 130 | 131 | 所以,简而言之,如果你能保证在每个线程中最多借一个对象,Stormpot 是最好的选择。如果您无法确保这一点,请使用在所有情况下都更加一致的 FOP。 132 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-tactile -------------------------------------------------------------------------------- /docs/1.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/1.xlsx -------------------------------------------------------------------------------- /docs/2.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/2.xlsx -------------------------------------------------------------------------------- /docs/b1-error-rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/b1-error-rate.png -------------------------------------------------------------------------------- /docs/b1-throughput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/b1-throughput.png -------------------------------------------------------------------------------- /docs/b2-error-rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/b2-error-rate.png -------------------------------------------------------------------------------- /docs/b2-throughput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/b2-throughput.png -------------------------------------------------------------------------------- /docs/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielYWoo/fast-object-pool/7d459b31618ae752a0ce46392993f6ced76b8d4c/docs/benchmark.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | Fast Object Pool 5 | https://github.com/DanielYWoo/fast-object-pool 6 | 4.0.0 7 | cn.danielw 8 | fast-object-pool 9 | jar 10 | 2.3.0 11 | An extremely fast object pool with zero dependencies 12 | 13 | UTF-8 14 | DanielYWoo_fast-object-pool 15 | danielywoo1 16 | https://sonarcloud.io 17 | 18 | 19 | 20 | 21 | Daniel 22 | Daniel 23 | daniel.y.woo@gmail.com 24 | https://github.com/DanielYWoo 25 | 26 | 27 | 28 | 29 | 30 | BSDLicense 31 | http://www.opensource.org/licenses/bsd-license.php 32 | 33 | 34 | Apache License, Version 2.0 35 | https://www.apache.org/licenses/LICENSE-2.0.txt 36 | 37 | 38 | 39 | 40 | Github 41 | https://github.com/DanielYWoo/fast-object-pool/issues?state=open 42 | 43 | 44 | 45 | 46 | ossrh 47 | https://oss.sonatype.org/content/repositories/snapshots 48 | 49 | 50 | ossrh 51 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 52 | 53 | 54 | 55 | 56 | 57 | com.conversantmedia 58 | disruptor 59 | 1.2.21 60 | 61 | true 62 | 63 | 64 | junit 65 | junit 66 | test 67 | 4.13.2 68 | 69 | 70 | org.apache.commons 71 | commons-pool2 72 | test 73 | 2.11.1 74 | 75 | 76 | com.github.chrisvest 77 | stormpot 78 | test 79 | 2.4 80 | 81 | 82 | nf.fr.eraasoft 83 | objectpool 84 | test 85 | 1.1.2 86 | 87 | 88 | 89 | 90 | https://github.com/DanielYWoo/fast-object-pool 91 | scm:git:git@github.com:DanielYWoo/fast-object-pool.git 92 | scm:git:git@github.com:DanielYWoo/fast-object-pool.git 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-compiler-plugin 100 | 3.13.0 101 | 102 | 11 103 | 11 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-source-plugin 109 | 3.3.1 110 | 111 | 112 | attach-sources 113 | 114 | jar-no-fork 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-javadoc-plugin 122 | 3.11.2 123 | 124 | 11 125 | 126 | 127 | 128 | attach-javadocs 129 | 130 | jar 131 | 132 | 133 | 134 | 135 | 136 | org.jacoco 137 | jacoco-maven-plugin 138 | 0.8.13 139 | 140 | 141 | 142 | prepare-agent 143 | 144 | 145 | 146 | report 147 | prepare-package 148 | 149 | report 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | gpg 160 | 161 | 162 | performRelease 163 | true 164 | 165 | 166 | 167 | 168 | 169 | org.apache.maven.plugins 170 | maven-gpg-plugin 171 | 3.2.7 172 | 173 | 174 | sign-artifacts 175 | verify 176 | 177 | sign 178 | 179 | 180 | 181 | 182 | 183 | org.sonatype.plugins 184 | nexus-staging-maven-plugin 185 | 1.7.0 186 | true 187 | 188 | ossrh 189 | https://oss.sonatype.org/ 190 | true 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /release-notes/v2.1.2.txt: -------------------------------------------------------------------------------- 1 | Features: 2 | - N/A 3 | 4 | Fixes: 5 | - Unnecessary wait in https://github.com/DanielYWoo/fast-object-pool/issues/25 6 | -------------------------------------------------------------------------------- /release-notes/v2.2.0.txt: -------------------------------------------------------------------------------- 1 | Features: 2 | - Now we have PoolInvalidObjectException and PoolExhaustedException. 3 | 4 | Fixes: 5 | - Disable the scavanger: https://github.com/DanielYWoo/fast-object-pool/pull/39 6 | 7 | Others: 8 | - Better SonarQube reports 9 | -------------------------------------------------------------------------------- /release-notes/v2.2.1.txt: -------------------------------------------------------------------------------- 1 | Features: 2 | - N/A 3 | 4 | Fixes: 5 | - Remove useless dependency:https://github.com/DanielYWoo/fast-object-pool/issues/51 6 | 7 | Others: 8 | - Better doc and unit test 9 | -------------------------------------------------------------------------------- /src/BUILD.md: -------------------------------------------------------------------------------- 1 | # Publish to Nexus on Mac OSX 2 | 3 | ## prepare the password 4 | update ~/.m2/settings.xml 5 | ``` 6 | 7 | 8 | 9 | ossrh 10 | your-jira-id 11 | your-jira-pwd 12 | 13 | 14 | 15 | ``` 16 | 17 | 18 | ## run the deployment with gpg profile 19 | ``` 20 | GPG_TTY=$(tty) 21 | export GPG_TTY 22 | mvn clean deploy -P gpg 23 | ``` -------------------------------------------------------------------------------- /src/benchmark/BaseWorker.java: -------------------------------------------------------------------------------- 1 | public abstract class BaseWorker extends Thread { 2 | 3 | private final Benchmark benchmark; 4 | private final int id; 5 | private final long loop; 6 | protected final int borrowsPerLoop; 7 | protected int simulateBlockingMs; 8 | long err = 0; 9 | 10 | public BaseWorker(Benchmark benchmark, int id, int borrowsPerLoop, long loop, int simulateBlockingMs) { 11 | this.benchmark = benchmark; 12 | this.id = id; 13 | this.loop = loop; 14 | this.borrowsPerLoop = borrowsPerLoop; 15 | this.simulateBlockingMs = simulateBlockingMs; 16 | } 17 | 18 | @Override public void run() { 19 | long t1 = System.currentTimeMillis(); 20 | for (int i = 0; i < loop; i++) { 21 | doSomething(); 22 | } 23 | long t2 = System.currentTimeMillis(); 24 | benchmark.latch.countDown(); 25 | synchronized (benchmark) { 26 | benchmark.statsAvgRespTime[id] = ((double) (t2 - t1)) / loop; 27 | benchmark.statsErrCount[id] = err; 28 | } 29 | } 30 | 31 | public abstract void doSomething(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/benchmark/Benchmark.java: -------------------------------------------------------------------------------- 1 | import java.text.DecimalFormat; 2 | import java.util.concurrent.CountDownLatch; 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | /** 6 | * @author Daniel 7 | */ 8 | public class Benchmark { 9 | 10 | final String name; 11 | final double[] statsAvgRespTime; 12 | final long[] statsErrCount; 13 | final private int workerCount; 14 | final private int loop; 15 | final CountDownLatch latch; 16 | final AtomicLong created = new AtomicLong(0); 17 | final int borrowsPerLoop; 18 | BaseWorker[] workers; 19 | 20 | Benchmark(String name, int workerCount, int borrowsPerLoop, int loop) { 21 | this.name = name; 22 | this.workerCount = workerCount; 23 | this.loop = loop; 24 | this.latch = new CountDownLatch(workerCount); 25 | this.borrowsPerLoop = borrowsPerLoop; 26 | this.statsAvgRespTime = new double[workerCount]; 27 | this.statsErrCount = new long[workerCount]; 28 | } 29 | 30 | public BenchmarkResult testAndPrint() throws InterruptedException { 31 | System.out.println(); 32 | System.out.println("Benchmark " + name + " with " + workerCount + " concurrent threads"); 33 | long t1 = System.currentTimeMillis(); 34 | for (int i = 0; i < workerCount; i++) { 35 | workers[i].start(); 36 | } 37 | latch.await(); 38 | long t2 = System.currentTimeMillis(); 39 | 40 | double stats = 0; 41 | for (int i = 0; i < workerCount; i++) { 42 | stats += statsAvgRespTime[i]; 43 | } 44 | double latency = stats / workerCount; 45 | // System.out.println("Average Response Time:" + new DecimalFormat("0.0000").format(latency)); 46 | 47 | stats = 0; 48 | for (int i = 0; i < workerCount; i++) { 49 | stats += statsErrCount[i]; 50 | } 51 | double errRate = stats * 100 / workerCount / loop; 52 | 53 | double throughput = (double) loop * workerCount / (t2 - t1); 54 | 55 | System.out.println("Throughput Per Second:" + new DecimalFormat("0").format(throughput) + "K"); 56 | // System.out.println("Objects created:" + created.get()); 57 | return new BenchmarkResult(name, workerCount, borrowsPerLoop, loop, created.get(), errRate, throughput, latency); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/benchmark/BenchmarkCommons.java: -------------------------------------------------------------------------------- 1 | import org.apache.commons.pool2.PooledObject; 2 | import org.apache.commons.pool2.PooledObjectFactory; 3 | import org.apache.commons.pool2.impl.DefaultPooledObject; 4 | import org.apache.commons.pool2.impl.GenericObjectPool; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Daniel 11 | */ 12 | public class BenchmarkCommons extends Benchmark { 13 | 14 | BenchmarkCommons(int workerCount, int borrowsPerLoop, int loop, int simulateBlockingMs) { 15 | super("common-pool", workerCount, borrowsPerLoop, loop); 16 | GenericObjectPool pool = new GenericObjectPool<>(new PooledObjectFactory<>() { 17 | @Override 18 | public PooledObject makeObject() { 19 | created.incrementAndGet(); 20 | return new DefaultPooledObject<>(new StringBuilder()); 21 | } 22 | 23 | @Override 24 | public void destroyObject(PooledObject pooledObject) { 25 | } 26 | 27 | @Override 28 | public boolean validateObject(PooledObject pooledObject) { 29 | return false; 30 | } 31 | 32 | @Override 33 | public void activateObject(PooledObject pooledObject) { 34 | } 35 | 36 | @Override 37 | public void passivateObject(PooledObject pooledObject) { 38 | } 39 | }); 40 | pool.setMinIdle(256); 41 | pool.setMaxIdle(256); 42 | pool.setMaxTotal(256); 43 | pool.setMinEvictableIdleTimeMillis(60 * 1000 * 5L); 44 | 45 | this.workers = new Worker[workerCount]; 46 | for (int i = 0; i < workerCount; i++) { 47 | workers[i] = new Worker(this, i, loop, borrowsPerLoop, simulateBlockingMs, pool); 48 | } 49 | } 50 | 51 | private static class Worker extends BaseWorker { 52 | 53 | private final GenericObjectPool pool; 54 | 55 | Worker(Benchmark benchmark, int id, int loop, int borrowsPerLoop, int simulateBlockingMs, GenericObjectPool pool) { 56 | super(benchmark, id, borrowsPerLoop, loop, simulateBlockingMs); 57 | this.pool = pool; 58 | } 59 | 60 | @Override public void doSomething() { 61 | List list = new ArrayList<>(); 62 | try { 63 | for (int i = 0; i < borrowsPerLoop; i++) { 64 | StringBuilder obj = pool.borrowObject(10); 65 | obj.append("X"); 66 | if (simulateBlockingMs > 0) Thread.sleep(simulateBlockingMs); // simulate thread blocking 67 | list.add(obj); 68 | } 69 | } catch (Exception e) { 70 | err++; 71 | } finally { 72 | list.forEach(o -> { 73 | try { 74 | pool.returnObject(o); 75 | } catch (Exception e) { 76 | err++; 77 | } 78 | }); 79 | } 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/benchmark/BenchmarkFastObjectPool.java: -------------------------------------------------------------------------------- 1 | import cn.danielw.fop.ObjectFactory; 2 | import cn.danielw.fop.ObjectPool; 3 | import cn.danielw.fop.PoolConfig; 4 | import cn.danielw.fop.Poolable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Daniel 11 | */ 12 | public class BenchmarkFastObjectPool extends Benchmark { 13 | 14 | BenchmarkFastObjectPool(String name, int workerCount, int borrows, int loop, int simulateBlockingMs) { 15 | super(name, workerCount, borrows, loop); 16 | PoolConfig config = new PoolConfig(); 17 | config.setPartitionSize(32); 18 | config.setMaxSize(16); 19 | config.setMinSize(16); 20 | config.setMaxIdleMilliseconds(60 * 1000 * 5); 21 | config.setMaxWaitMilliseconds(10); 22 | 23 | ObjectFactory factory = new ObjectFactory<>() { 24 | @Override 25 | public StringBuilder create() { 26 | created.incrementAndGet(); 27 | return new StringBuilder(); 28 | } 29 | @Override 30 | public void destroy(StringBuilder o) { 31 | } 32 | @Override 33 | public boolean validate(StringBuilder o) { 34 | return true; 35 | } 36 | }; 37 | ObjectPool pool = new ObjectPool<>(config, factory); 38 | workers = new Worker[workerCount]; 39 | for (int i = 0; i < workerCount; i++) { 40 | workers[i] = new Worker(this, i, borrows, loop, simulateBlockingMs, pool); 41 | } 42 | } 43 | 44 | protected static class Worker extends BaseWorker { 45 | 46 | private final ObjectPool pool; 47 | 48 | Worker(Benchmark benchmark, int id, int borrows, long loop, int simulateBlockingMs, ObjectPool pool) { 49 | super(benchmark, id, borrows, loop, simulateBlockingMs); 50 | this.pool = pool; 51 | } 52 | 53 | @Override public void doSomething() { 54 | List> list = new ArrayList<>(); 55 | try { 56 | for (int i = 0; i < borrowsPerLoop; i++) { 57 | Poolable obj = pool.borrowObject(false); 58 | obj.getObject().append("X"); 59 | if (simulateBlockingMs > 0) Thread.sleep(simulateBlockingMs); // simulate thread blocking 60 | list.add(obj); 61 | } 62 | } catch (Exception e) { 63 | err++; 64 | } finally { 65 | list.forEach(o -> { 66 | try { 67 | pool.returnObject(o); 68 | } catch (Exception e) { 69 | err++; 70 | } 71 | }); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/benchmark/BenchmarkFastObjectPoolDisruptor.java: -------------------------------------------------------------------------------- 1 | import cn.danielw.fop.DisruptorObjectPool; 2 | import cn.danielw.fop.ObjectFactory; 3 | import cn.danielw.fop.PoolConfig; 4 | 5 | /** 6 | * @author Daniel 7 | */ 8 | public class BenchmarkFastObjectPoolDisruptor extends Benchmark { 9 | 10 | BenchmarkFastObjectPoolDisruptor(int workerCount, int borrows, int loop, int simulateBlockingMs) { 11 | super("fop", workerCount, borrows, loop); 12 | PoolConfig config = new PoolConfig(); 13 | config.setPartitionSize(32); 14 | config.setMaxSize(16); 15 | config.setMinSize(16); 16 | config.setMaxIdleMilliseconds(60 * 1000 * 5); 17 | config.setMaxWaitMilliseconds(10); 18 | 19 | ObjectFactory factory = new ObjectFactory<>() { 20 | @Override 21 | public StringBuilder create() { 22 | created.incrementAndGet(); 23 | return new StringBuilder(); 24 | } 25 | @Override 26 | public void destroy(StringBuilder o) { 27 | } 28 | @Override 29 | public boolean validate(StringBuilder o) { 30 | return true; 31 | } 32 | }; 33 | DisruptorObjectPool pool = new DisruptorObjectPool<>(config, factory); 34 | workers = new BenchmarkFastObjectPool.Worker[workerCount]; 35 | for (int i = 0; i < workerCount; i++) { 36 | workers[i] = new BenchmarkFastObjectPool.Worker(this, i, borrows, loop, simulateBlockingMs, pool); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/benchmark/BenchmarkFurious.java: -------------------------------------------------------------------------------- 1 | import nf.fr.eraasoft.pool.ObjectPool; 2 | import nf.fr.eraasoft.pool.PoolSettings; 3 | import nf.fr.eraasoft.pool.PoolableObjectBase; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @author Daniel 10 | */ 11 | public class BenchmarkFurious extends Benchmark { 12 | 13 | BenchmarkFurious(int workerCount, int borrows, int loop, int simulateBlockingMs) { 14 | super("furious", workerCount, borrows, loop); 15 | 16 | // Create your PoolSettings with an instance of PoolableObject 17 | PoolSettings poolSettings = new PoolSettings<>(new PoolableObjectBase<>() { 18 | @Override 19 | public StringBuilder make() { 20 | created.incrementAndGet(); 21 | return new StringBuilder(); 22 | } 23 | 24 | @Override 25 | public void activate(StringBuilder t) { 26 | t.setLength(0); 27 | } 28 | }); 29 | // Add some settings 30 | poolSettings.min(256).max(256); 31 | 32 | // Get the objectPool instance using a Singleton Design Pattern is a good idea 33 | ObjectPool pool = poolSettings.pool(); 34 | 35 | workers = new Worker[workerCount]; 36 | for (int i = 0; i < workerCount; i++) { 37 | workers[i] = new Worker(this, i, borrows, loop, simulateBlockingMs, pool); 38 | } 39 | } 40 | 41 | private static class Worker extends BaseWorker { 42 | 43 | private final ObjectPool pool; 44 | 45 | Worker(Benchmark benchmark, int id, int borrows, long loop, int simulateBlockingMs, ObjectPool pool) { 46 | super(benchmark, id, borrows, loop, simulateBlockingMs); 47 | this.pool = pool; 48 | } 49 | 50 | @Override public void doSomething() { 51 | List list = new ArrayList<>(); 52 | try { 53 | for (int i = 0; i < borrowsPerLoop; i++) { 54 | StringBuilder obj = pool.getObj(); // NO timeout at method level 55 | obj.append("X"); 56 | if (simulateBlockingMs > 0) Thread.sleep(simulateBlockingMs); // simulate thread blocking 57 | list.add(obj); 58 | } 59 | } catch (Exception e) { 60 | err++; 61 | } finally { 62 | list.forEach(o -> { 63 | try { 64 | pool.returnObj(o); 65 | } catch (Exception e) { 66 | err++; 67 | } 68 | }); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/benchmark/BenchmarkResult.java: -------------------------------------------------------------------------------- 1 | public class BenchmarkResult { 2 | 3 | private final String poolName; 4 | private final long created; 5 | private final double errorRate; 6 | private final double avgThroughput; 7 | private final double avgLatency; 8 | private final int threads; 9 | private final int borrows; 10 | private final long loops; 11 | 12 | public BenchmarkResult(String poolName, 13 | int threads, int borrows, long loops, 14 | long created, double errorRate, double avgThroughput, double avgLatency) { 15 | this.poolName = poolName; 16 | this.threads = threads; 17 | this.borrows = borrows; 18 | this.loops = loops; 19 | this.created = created; 20 | this.errorRate = errorRate; 21 | this.avgThroughput = avgThroughput; 22 | this.avgLatency = avgLatency; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | // return poolName + 28 | // "," + threads + 29 | // "," + borrows + 30 | // "," + loops + 31 | // "," + created + 32 | // "," + plotLegend() + 33 | // "," + errorRate + 34 | // "," + avgThroughput; 35 | return threads + "/" + poolName + "," + errorRate + "," + avgThroughput; 36 | } 37 | 38 | public String getPoolName() { 39 | return poolName; 40 | } 41 | 42 | public double getErrorRate() { 43 | return errorRate; 44 | } 45 | 46 | public double getAvgThroughput() { 47 | return avgThroughput; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/benchmark/BenchmarkStormpot.java: -------------------------------------------------------------------------------- 1 | import stormpot.*; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * @author Daniel 12 | */ 13 | public class BenchmarkStormpot extends Benchmark { 14 | 15 | private static final Set slots = Collections.newSetFromMap(new ConcurrentHashMap<>()); 16 | 17 | static class MyPoolable implements stormpot.Poolable { 18 | 19 | private final Slot slot; 20 | private final StringBuilder test; 21 | 22 | MyPoolable(Slot slot) { 23 | test = new StringBuilder(); 24 | this.slot = slot; 25 | slots.add(slot); 26 | } 27 | 28 | StringBuilder getTest() { 29 | return test; 30 | } 31 | 32 | @Override 33 | public void release() { 34 | slot.release(this); 35 | } 36 | } 37 | 38 | 39 | BenchmarkStormpot(int workerCount, int borrows, int loop, int simulateBlockingMs) { 40 | super("Stormpot", workerCount, borrows, loop); 41 | 42 | Config config = new Config<>().setAllocator(new Allocator() { 43 | @Override 44 | public MyPoolable allocate(Slot slot) { 45 | created.incrementAndGet(); 46 | return new MyPoolable(slot); 47 | } 48 | 49 | @Override 50 | public void deallocate(MyPoolable x) { 51 | 52 | } 53 | }).setSize(256); 54 | Pool pool = new BlazePool<>(config); 55 | workers = new Worker[workerCount]; 56 | for (int i = 0; i < workerCount; i++) { 57 | workers[i] = new Worker(this, i, borrows, loop, simulateBlockingMs, pool); 58 | } 59 | System.out.println("slots:" + slots.size()); 60 | } 61 | 62 | private static class Worker extends BaseWorker { 63 | 64 | private static final Timeout TIMEOUT = new Timeout(10, TimeUnit.MILLISECONDS); 65 | private final Pool pool; 66 | 67 | Worker(Benchmark benchmark, int id, int borrows, int loop, int simulateBlockingMs, Pool pool) { 68 | super(benchmark, id, borrows, loop, simulateBlockingMs); 69 | this.pool = pool; 70 | } 71 | 72 | @Override public void doSomething() { 73 | List list = new ArrayList<>(); 74 | try { 75 | for (int i = 0; i < borrowsPerLoop; i++) { 76 | MyPoolable obj = pool.claim(TIMEOUT); 77 | obj.getTest().append("x"); 78 | if (simulateBlockingMs > 0) Thread.sleep(simulateBlockingMs); // simulate thread blocking 79 | list.add(obj); 80 | } 81 | } catch (Exception e) { 82 | err++; 83 | } finally { 84 | list.forEach(o -> { 85 | try { 86 | o.release(); 87 | } catch (Exception e) { 88 | err++; 89 | } 90 | }); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/benchmark/Start.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.util.stream.Collectors; 3 | 4 | public class Start { 5 | 6 | // key: threads 7 | // value: pool-name and throughput 8 | private static final Map> throughputByThreads = new TreeMap<>(); 9 | // key: threads 10 | // value: pool-name and error rate 11 | private static final Map> errorRateByThreads = new TreeMap<>(); 12 | 13 | public static void main(String[] args) throws Exception { 14 | int simulateBlockingMs = 0; 15 | System.out.println("-----------warm up------------"); 16 | // testFOP(50, 1, 1000, simulateBlockingMs); 17 | testFOPDisruptor(50, 1, 1000, simulateBlockingMs); 18 | testStormpot(50, 1, 1000, simulateBlockingMs); 19 | testFurious(50, 1, 1000, simulateBlockingMs); 20 | testCommon(50, 1, 1000, simulateBlockingMs); 21 | 22 | System.out.println("-----------warm up done, start test ------------"); 23 | 24 | System.out.println("----------- borrow 1 ------------"); 25 | throughputByThreads.clear(); 26 | testAll(1, simulateBlockingMs); 27 | printResult(); 28 | 29 | System.out.println("----------- borrow 2 ------------"); 30 | throughputByThreads.clear(); 31 | testAll(2, simulateBlockingMs); 32 | printResult(); 33 | 34 | System.exit(0); 35 | } 36 | 37 | private static void printResult() { 38 | List poolNames = new ArrayList<>(throughputByThreads.values().stream().findFirst().get().keySet()); 39 | System.out.println("throughput result:"); 40 | System.out.println("threads," + poolNames); 41 | throughputByThreads.forEach((threads, value) -> { 42 | System.out.print(threads + ","); 43 | poolNames.forEach(name -> System.out.print(value.getOrDefault(name, 0D) + ",")); 44 | System.out.println(); 45 | }); 46 | 47 | System.out.println("error rate result:"); 48 | System.out.println("threads," + poolNames); 49 | errorRateByThreads.forEach((threads, value) -> { 50 | System.out.print(threads + ","); 51 | poolNames.forEach(name -> System.out.print(value.getOrDefault(name, 0D) + ",")); 52 | System.out.println(); 53 | }); 54 | } 55 | 56 | private static void testAll(int borrows, int simulateBlockingMs) throws Exception { 57 | // System.out.println("-----------fast object pool (borrow " + borrows + " objects each time)------------"); 58 | // testFOP(borrows); 59 | System.out.println("-----------fast object pool with disruptor (borrow " + borrows + " object each time)------------"); 60 | testFOPDisruptor(borrows, simulateBlockingMs); 61 | 62 | System.out.println("-----------stormpot object pool (borrow " + borrows + " object each time)------------"); 63 | testStormpot(borrows, simulateBlockingMs); 64 | 65 | System.out.println("-----------furious object pool (borrow " + borrows + " object each time)------------"); 66 | testFurious(borrows, simulateBlockingMs); 67 | 68 | System.out.println("------------Apache commons pool (borrow " + borrows + " object each time)-----------"); 69 | testCommon(borrows, simulateBlockingMs); 70 | } 71 | 72 | private static void testCommon(int borrows, int simulateBlockingMs) throws Exception { 73 | // too slow, so less loops 74 | testCommon(50, borrows, 20000, simulateBlockingMs); 75 | testCommon(100, borrows, 10000, simulateBlockingMs); 76 | // testCommon(150, borrows, 9000, simulateBlockingMs); 77 | testCommon(200, borrows, 8000, simulateBlockingMs); 78 | // testCommon(250, borrows, 7000, simulateBlockingMs); 79 | // testCommon(300, borrows, 6000, simulateBlockingMs); 80 | // testCommon(350, borrows, 5000, simulateBlockingMs); 81 | testCommon(400, borrows, 4000, simulateBlockingMs); 82 | // testCommon(450, borrows, 3000, simulateBlockingMs); 83 | // testCommon(500, borrows, 2000, simulateBlockingMs); 84 | // testCommon(550, borrows, 1000, simulateBlockingMs); 85 | testCommon(600, borrows, 1000, simulateBlockingMs); 86 | } 87 | 88 | private static void testFurious(int borrows, int simulateBlockingMs) throws InterruptedException { 89 | if (borrows > 1) { 90 | System.out.println("Furious cannot set max wait time, so it will hang with deadlock if get more than 1 object concurrently"); 91 | return; 92 | } 93 | testFurious(50, borrows, 50000, simulateBlockingMs); 94 | testFurious(100, borrows, 50000, simulateBlockingMs); 95 | // testFurious(150, borrows, 50000, simulateBlockingMs); 96 | testFurious(200, borrows, 30000, simulateBlockingMs); 97 | // testFurious(250, borrows, 30000, simulateBlockingMs); 98 | // testFurious(300, borrows, 30000, simulateBlockingMs); 99 | // testFurious(350, borrows, 20000, simulateBlockingMs); 100 | testFurious(400, borrows, 20000, simulateBlockingMs); 101 | // testFurious(450, borrows, 20000, simulateBlockingMs); 102 | // testFurious(500, borrows, 10000, simulateBlockingMs); 103 | // testFurious(550, borrows, 10000, simulateBlockingMs); 104 | testFurious(600, borrows, 10000, simulateBlockingMs); 105 | } 106 | 107 | private static void testStormpot(int borrows, int simulateBlockingMs) throws InterruptedException { 108 | testStormpot(50, borrows, 50000, simulateBlockingMs); 109 | testStormpot(100, borrows, 50000, simulateBlockingMs); 110 | // testStormpot(150, borrows, 50000); 111 | if (borrows > 1) { 112 | return; // this is too slow, skip it 113 | } 114 | testStormpot(200, borrows, 30000, simulateBlockingMs); 115 | // testStormpot(250, borrows, 30000); 116 | // testStormpot(300, borrows, 30000); 117 | // testStormpot(350, borrows, 20000); 118 | testStormpot(400, borrows, 20000, simulateBlockingMs); 119 | // testStormpot(450, borrows, 20000); 120 | // testStormpot(500, borrows, 10000); 121 | // testStormpot(550, borrows, 10000); 122 | testStormpot(600, borrows, 10000, simulateBlockingMs); 123 | } 124 | 125 | 126 | private static void testFOP(int borrowsPerLoop, int simulateBlockingMs) throws InterruptedException { 127 | testFOP(50, borrowsPerLoop, 50000, simulateBlockingMs); 128 | testFOP(100, borrowsPerLoop, 50000, simulateBlockingMs); 129 | testFOP(150, borrowsPerLoop, 50000, simulateBlockingMs); 130 | testFOP(200, borrowsPerLoop, 30000, simulateBlockingMs); 131 | testFOP(250, borrowsPerLoop, 30000, simulateBlockingMs); 132 | testFOP(300, borrowsPerLoop, 30000, simulateBlockingMs); 133 | testFOP(350, borrowsPerLoop, 20000, simulateBlockingMs); 134 | testFOP(400, borrowsPerLoop, 20000, simulateBlockingMs); 135 | testFOP(450, borrowsPerLoop, 20000, simulateBlockingMs); 136 | testFOP(500, borrowsPerLoop, 10000, simulateBlockingMs); 137 | testFOP(550, borrowsPerLoop, 10000, simulateBlockingMs); 138 | testFOP(600, borrowsPerLoop, 10000, simulateBlockingMs); 139 | } 140 | 141 | private static void testFOPDisruptor(int borrows, int simulateBlockingMs) throws InterruptedException { 142 | testFOPDisruptor(50, borrows, 50000, simulateBlockingMs); 143 | testFOPDisruptor(100,borrows, 50000, simulateBlockingMs); 144 | // testFOPDisruptor(150,borrows, 50000, simulateBlockingMs); 145 | testFOPDisruptor(200,borrows, 30000, simulateBlockingMs); 146 | // testFOPDisruptor(250,borrows, 30000, simulateBlockingMs); 147 | // testFOPDisruptor(300,borrows, 30000, simulateBlockingMs); 148 | // testFOPDisruptor(350,borrows, 20000, simulateBlockingMs); 149 | testFOPDisruptor(400,borrows, 20000, simulateBlockingMs); 150 | // testFOPDisruptor(450,borrows, 20000, simulateBlockingMs); 151 | // testFOPDisruptor(500,borrows, 10000, simulateBlockingMs); 152 | // testFOPDisruptor(550,borrows, 10000, simulateBlockingMs); 153 | testFOPDisruptor(600,borrows, 10000, simulateBlockingMs); 154 | } 155 | 156 | private static void testFOP(int workerCount, int borrowsPerLoop, int loop, int simulateBlockingMs) throws InterruptedException { 157 | BenchmarkResult result = new BenchmarkFastObjectPool("fop-nod", workerCount, borrowsPerLoop, loop, simulateBlockingMs).testAndPrint(); 158 | throughputByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getAvgThroughput()); 159 | errorRateByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getErrorRate()); 160 | cleanup(); 161 | } 162 | 163 | private static void testFOPDisruptor(int workerCount, int borrowsPerLoop, int loop, int simulateBlockingMs) throws InterruptedException { 164 | BenchmarkResult result = new BenchmarkFastObjectPoolDisruptor(workerCount, borrowsPerLoop, loop, simulateBlockingMs).testAndPrint(); 165 | throughputByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getAvgThroughput()); 166 | errorRateByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getErrorRate()); 167 | cleanup(); 168 | } 169 | 170 | private static void testStormpot(int workerCount, int borrowsPerLoop, int loop, int simulateBlockingMs) throws InterruptedException { 171 | BenchmarkResult result = new BenchmarkStormpot(workerCount, borrowsPerLoop, loop, simulateBlockingMs).testAndPrint(); 172 | throughputByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getAvgThroughput()); 173 | errorRateByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getErrorRate()); 174 | cleanup(); 175 | } 176 | 177 | private static void testFurious(int workerCount, int borrowsPerLoop, int loop, int simulateBlockingMs) throws InterruptedException { 178 | BenchmarkResult result = new BenchmarkFurious(workerCount, borrowsPerLoop, loop, simulateBlockingMs).testAndPrint(); 179 | throughputByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getAvgThroughput()); 180 | errorRateByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getErrorRate()); 181 | cleanup(); 182 | } 183 | 184 | private static void testCommon(int workerCount, int borrowsPerLoop, int loop, int simulateBlockingMs) throws Exception { 185 | BenchmarkResult result = new BenchmarkCommons(workerCount, borrowsPerLoop, loop, simulateBlockingMs).testAndPrint(); 186 | throughputByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getAvgThroughput()); 187 | errorRateByThreads.computeIfAbsent(workerCount, k -> new TreeMap<>()).put(result.getPoolName(), result.getErrorRate()); 188 | cleanup(); 189 | } 190 | 191 | private static void cleanup() { 192 | try { 193 | System.out.println("cleaning up ..."); 194 | Thread.sleep(1000L * 2); 195 | System.gc(); 196 | Thread.sleep(1000L * 2); 197 | } catch (InterruptedException e) { 198 | e.printStackTrace(); 199 | Thread.currentThread().interrupt(); 200 | } 201 | System.out.println(); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/DisruptorObjectPool.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | import com.conversantmedia.util.concurrent.DisruptorBlockingQueue; 4 | 5 | import java.util.concurrent.BlockingQueue; 6 | 7 | /** 8 | * This pool has disruptor as the underlying blocking queue. 9 | */ 10 | public class DisruptorObjectPool extends ObjectPool { 11 | 12 | /** 13 | * build a disruptor-based pool for better performance. 14 | */ 15 | public DisruptorObjectPool(PoolConfig poolConfig, ObjectFactory objectFactory) { 16 | super(poolConfig, objectFactory); 17 | } 18 | 19 | /** 20 | * create a disruptor-based blocking queue for better performance 21 | */ 22 | @Override 23 | protected BlockingQueue> createBlockingQueue(PoolConfig config) { 24 | return new DisruptorBlockingQueue<>(config.getMaxSize()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/ObjectFactory.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | /** 4 | * @author Daniel 5 | */ 6 | public interface ObjectFactory { 7 | 8 | /** 9 | * @return the object to be created 10 | */ 11 | T create(); 12 | 13 | /** 14 | * destroy the object gracefully. 15 | */ 16 | void destroy(T t); 17 | 18 | /** 19 | * validate the object before returning to the consumer. Note, the validation must be fast 20 | */ 21 | boolean validate(T t); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/ObjectPool.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.logging.Level; 7 | import java.util.logging.Logger; 8 | 9 | /** 10 | * @author Daniel 11 | */ 12 | public class ObjectPool { 13 | 14 | private static final Logger logger = Logger.getLogger(ObjectPool.class.getCanonicalName()); 15 | 16 | private final PoolConfig config; 17 | private final ObjectFactory factory; 18 | private final ObjectPoolPartition[] partitions; 19 | private Scavenger scavenger; 20 | private volatile boolean shuttingDown; 21 | 22 | public ObjectPool(PoolConfig poolConfig, ObjectFactory objectFactory) { 23 | this.config = poolConfig; 24 | this.factory = objectFactory; 25 | this.partitions = new ObjectPoolPartition[config.getPartitionSize()]; 26 | for (int i = 0; i < config.getPartitionSize(); i++) { 27 | partitions[i] = new ObjectPoolPartition<>(this, i, config, objectFactory, createBlockingQueue(poolConfig)); 28 | } 29 | if (config.getScavengeIntervalMilliseconds() > 0) { 30 | this.scavenger = new Scavenger(); 31 | this.scavenger.start(); 32 | } 33 | } 34 | 35 | /** 36 | * the pool needs a blocking queue for consumers to wait for an available object. The queue can be in different 37 | * implementations in subclasses. 38 | */ 39 | protected BlockingQueue> createBlockingQueue(PoolConfig poolConfig) { 40 | return new ArrayBlockingQueue<>(poolConfig.getMaxSize()); 41 | } 42 | 43 | /** 44 | * borrow an object from the pool. the call will be blocked for at most PoolConfig.maxWaitMilliseconds 45 | * before throwing an Exception 46 | * @return the object 47 | */ 48 | public Poolable borrowObject() { 49 | return borrowObject(true); 50 | } 51 | 52 | /** 53 | * borrow an object from the pool 54 | * @param noTimeout if true, the call will be blocked until one is available; 55 | * if false, the call will be blocked for at most PoolConfig.maxWaitMilliseconds 56 | * before throwing an Exception 57 | * @return the object 58 | */ 59 | public Poolable borrowObject(boolean noTimeout) { 60 | for (int i = 0; i < 3; i++) { // try at most three times 61 | Poolable result = getObject(noTimeout); 62 | if (factory.validate(result.getObject())) { 63 | return result; 64 | } else { 65 | logger.warning("Invalid object found in the pool, destroy it: " + result.getObject()); 66 | this.partitions[result.getPartition()].decreaseObject(result); 67 | } 68 | } 69 | throw new PoolInvalidObjectException(); 70 | } 71 | 72 | @SuppressWarnings({"java:S112", "java:S2142"}) 73 | private Poolable getObject(boolean noTimeout) { 74 | if (shuttingDown) { 75 | throw new IllegalStateException("Your pool is shutting down"); 76 | } 77 | int partition = (int) (Thread.currentThread().getId() % this.config.getPartitionSize()); 78 | ObjectPoolPartition subPool = this.partitions[partition]; 79 | Poolable freeObject; 80 | do { // loop to ensure: if T1 increases an object but T2 takes it, then T1 can poll and increase it again 81 | freeObject = subPool.getObjectQueue().poll(); 82 | if (freeObject == null && subPool.increaseObjects(1) <= 0) { // full, have to wait 83 | freeObject = waitWhenSubPoolIsFull(noTimeout, subPool); 84 | } 85 | } while (freeObject == null); 86 | freeObject.setLastAccessTs(System.currentTimeMillis()); 87 | return freeObject; 88 | } 89 | 90 | private Poolable waitWhenSubPoolIsFull(boolean noTimeout, ObjectPoolPartition subPool) { 91 | Poolable freeObject; 92 | try { 93 | if (noTimeout) { 94 | freeObject = subPool.getObjectQueue().take(); 95 | } else { 96 | freeObject = subPool.getObjectQueue().poll(config.getMaxWaitMilliseconds(), TimeUnit.MILLISECONDS); 97 | if (freeObject == null) { 98 | throw new PoolExhaustedException(); 99 | } 100 | } 101 | } catch (InterruptedException e) { 102 | throw new RuntimeException(e); // will never happen 103 | } 104 | return freeObject; 105 | } 106 | 107 | @SuppressWarnings({"java:S112", "java:S2142"}) 108 | public void returnObject(Poolable obj) { 109 | ObjectPoolPartition subPool = this.partitions[obj.getPartition()]; 110 | try { 111 | subPool.getObjectQueue().put(obj); 112 | if (logger.isLoggable(Level.FINE)) 113 | logger.fine("return object: queue size:" + subPool.getObjectQueue().size() + 114 | ", partition id:" + obj.getPartition()); 115 | } catch (InterruptedException e) { 116 | throw new RuntimeException(e); // impossible for now, unless there is a bug, e,g. borrow once but return twice. 117 | } 118 | } 119 | 120 | public int getSize() { 121 | int size = 0; 122 | for (ObjectPoolPartition subPool : partitions) { 123 | size += subPool.getTotalCount(); 124 | } 125 | return size; 126 | } 127 | 128 | public synchronized int shutdown() throws InterruptedException { 129 | shuttingDown = true; 130 | int removed = 0; 131 | if (scavenger != null) { 132 | scavenger.interrupt(); 133 | scavenger.join(); 134 | } 135 | for (ObjectPoolPartition partition : partitions) { 136 | removed += partition.shutdown(); 137 | } 138 | return removed; 139 | } 140 | 141 | private class Scavenger extends Thread { 142 | 143 | @Override @SuppressWarnings({"java:S2142", "java:S108"}) 144 | public void run() { 145 | int partition = 0; 146 | while (!ObjectPool.this.shuttingDown) { 147 | try { 148 | //noinspection BusyWait 149 | Thread.sleep(config.getScavengeIntervalMilliseconds()); 150 | partition = ++partition % config.getPartitionSize(); 151 | if (logger.isLoggable(Level.FINE)) { 152 | logger.fine("scavenge sub pool " + partition); 153 | } 154 | partitions[partition].scavenge(); 155 | } catch (InterruptedException ignored) { 156 | } 157 | } 158 | } 159 | 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/ObjectPoolPartition.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | import java.util.concurrent.BlockingQueue; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | import java.util.logging.Level; 6 | import java.util.logging.Logger; 7 | 8 | /** 9 | * @author Daniel 10 | */ 11 | public class ObjectPoolPartition { 12 | 13 | private static final Logger logger = Logger.getLogger(ObjectPoolPartition.class.getName()); 14 | 15 | private final ObjectPool pool; 16 | private final PoolConfig config; 17 | private final int partition; 18 | private final BlockingQueue> objectQueue; 19 | private final ObjectFactory objectFactory; 20 | private int totalCount; 21 | 22 | public ObjectPoolPartition(ObjectPool pool, int partition, PoolConfig config, 23 | ObjectFactory objectFactory, BlockingQueue> queue) { 24 | this.pool = pool; 25 | this.config = config; 26 | this.objectFactory = objectFactory; 27 | this.partition = partition; 28 | this.objectQueue = queue; 29 | for (int i = 0; i < config.getMinSize(); i++) { 30 | objectQueue.add(new Poolable<>(objectFactory.create(), pool, partition)); 31 | } 32 | totalCount = config.getMinSize(); 33 | } 34 | 35 | public BlockingQueue> getObjectQueue() { 36 | return objectQueue; 37 | } 38 | 39 | /** 40 | * @param delta the number to increase 41 | * @return the actual number of increased objects 42 | */ 43 | @SuppressWarnings({"java:S112", "java:S2142"}) 44 | public synchronized int increaseObjects(int delta) { 45 | if (delta + totalCount > config.getMaxSize()) { 46 | delta = config.getMaxSize() - totalCount; 47 | } 48 | try { 49 | for (int i = 0; i < delta; i++) { 50 | objectQueue.put(new Poolable<>(objectFactory.create(), pool, partition)); 51 | } 52 | totalCount += delta; 53 | if (logger.isLoggable(Level.FINE)) 54 | logger.fine("increase objects: count=" + totalCount + ", queue size=" + objectQueue.size()); 55 | } catch (InterruptedException e) { 56 | throw new RuntimeException(e); 57 | } 58 | return delta; 59 | } 60 | 61 | public synchronized boolean decreaseObject(Poolable obj) { 62 | objectFactory.destroy(obj.getObject()); 63 | totalCount--; 64 | return true; 65 | } 66 | 67 | public synchronized int getTotalCount() { 68 | return totalCount; 69 | } 70 | 71 | // set the scavenge interval carefully 72 | public synchronized void scavenge() throws InterruptedException { 73 | int delta = this.totalCount - config.getMinSize(); 74 | if (delta <= 0) return; 75 | int removed = 0; 76 | long now = System.currentTimeMillis(); 77 | Poolable obj; 78 | while (delta-- > 0 && (obj = objectQueue.poll()) != null) { 79 | // performance trade off: delta always decrease even if the queue is empty, 80 | // so it could take several intervals to shrink the pool to the configured min value. 81 | if (logger.isLoggable(Level.FINE)) 82 | logger.fine("obj=" + obj + ", now-last=" + (now - obj.getLastAccessTs()) + ", max idle=" + 83 | config.getMaxIdleMilliseconds()); 84 | if (now - obj.getLastAccessTs() > config.getMaxIdleMilliseconds() && 85 | ThreadLocalRandom.current().nextDouble(1) < config.getScavengeRatio()) { 86 | decreaseObject(obj); // shrink the pool size if the object reaches max idle time 87 | removed++; 88 | } else { 89 | objectQueue.put(obj); //put it back 90 | } 91 | } 92 | if (removed > 0 && logger.isLoggable(Level.FINE)) logger.fine(removed + " objects were scavenged."); 93 | } 94 | 95 | public synchronized int shutdown() { 96 | int removed = 0; 97 | long startTs = System.currentTimeMillis(); 98 | while (this.totalCount > 0 && System.currentTimeMillis() - startTs < config.getShutdownWaitMilliseconds()) { 99 | Poolable obj = objectQueue.poll(); 100 | if (obj != null) { 101 | decreaseObject(obj); 102 | removed++; 103 | } 104 | } 105 | return removed; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/PoolConfig.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | /** 4 | * @author Daniel 5 | */ 6 | public class PoolConfig { 7 | 8 | private int maxWaitMilliseconds = 5000; // when pool is full, wait at most 5 seconds, then throw an exception 9 | private int maxIdleMilliseconds = 300000; // objects idle for 5 minutes will be destroyed to shrink the pool size 10 | private int minSize = 5; 11 | private int maxSize = 20; 12 | private int partitionSize = 4; 13 | private int scavengeIntervalMilliseconds = 1000 * 60 * 2; 14 | private double scavengeRatio = 0.5; // avoid cleaning up all connections in the pool at the same time 15 | private int shutdownWaitMilliseconds = 1000 * 30; 16 | 17 | public int getMaxWaitMilliseconds() { 18 | return maxWaitMilliseconds; 19 | } 20 | 21 | /** 22 | * this is only used for blocking call to borrowObject(true). 23 | * @param maxWaitMilliseconds how long to block 24 | * @return the pool config 25 | */ 26 | public PoolConfig setMaxWaitMilliseconds(int maxWaitMilliseconds) { 27 | if (maxWaitMilliseconds <= 0) { 28 | throw new IllegalArgumentException("Cannot set max wait time to a negative number " + maxWaitMilliseconds); 29 | } 30 | this.maxWaitMilliseconds = maxWaitMilliseconds; 31 | return this; 32 | } 33 | 34 | public int getMinSize() { 35 | return minSize; 36 | } 37 | 38 | public PoolConfig setMinSize(int minSize) { 39 | this.minSize = minSize; 40 | return this; 41 | } 42 | 43 | public int getMaxSize() { 44 | return maxSize; 45 | } 46 | 47 | public PoolConfig setMaxSize(int maxSize) { 48 | this.maxSize = maxSize; 49 | return this; 50 | } 51 | 52 | public int getMaxIdleMilliseconds() { 53 | return maxIdleMilliseconds; 54 | } 55 | 56 | public PoolConfig setMaxIdleMilliseconds(int maxIdleMilliseconds) { 57 | this.maxIdleMilliseconds = maxIdleMilliseconds; 58 | return this; 59 | } 60 | 61 | public int getPartitionSize() { 62 | return partitionSize; 63 | } 64 | 65 | public PoolConfig setPartitionSize(int partitionSize) { 66 | this.partitionSize = partitionSize; 67 | return this; 68 | } 69 | 70 | public int getScavengeIntervalMilliseconds() { 71 | return scavengeIntervalMilliseconds; 72 | } 73 | 74 | /** 75 | * @param scavengeIntervalMilliseconds set it to zero if you don't want to automatically shrink your pool. 76 | * This is useful for fixed-size pool, or pools don't increase too much. 77 | * @return the pool config 78 | */ 79 | public PoolConfig setScavengeIntervalMilliseconds(int scavengeIntervalMilliseconds) { 80 | if (scavengeIntervalMilliseconds != 0 && scavengeIntervalMilliseconds < 5000) { 81 | throw new IllegalArgumentException("Cannot set interval too short (" + scavengeIntervalMilliseconds + 82 | "), must be at least 5 seconds, or zero to disable scavenger"); 83 | } 84 | this.scavengeIntervalMilliseconds = scavengeIntervalMilliseconds; 85 | return this; 86 | } 87 | 88 | public double getScavengeRatio() { 89 | return scavengeRatio; 90 | } 91 | 92 | /** 93 | * Each time we shrink a pool, we only scavenge some of the objects to avoid an empty pool 94 | * @param scavengeRatio must be a double between (0, 1] 95 | * @return the pool config 96 | */ 97 | public PoolConfig setScavengeRatio(double scavengeRatio) { 98 | if (scavengeRatio <= 0 || scavengeRatio > 1) { 99 | throw new IllegalArgumentException("Invalid scavenge ratio: " + scavengeRatio); 100 | } 101 | this.scavengeRatio = scavengeRatio; 102 | return this; 103 | } 104 | 105 | public int getShutdownWaitMilliseconds() { 106 | return shutdownWaitMilliseconds; 107 | } 108 | 109 | /** 110 | * If any borrowed objects are leaked and cannot be returned, the pool will be shut down after 111 | * partitions * shutdownWaitMilliseconds milliseconds. 112 | * If any borrowed objects are in use and cannot be returned to the pool timely 113 | * within partitions * shutdownWaitMilliseconds milliseconds, 114 | * the pool will be shut down and the objects in use will not be returned. 115 | * @param shutdownWaitMilliseconds default to 30 seconds for each partition 116 | */ 117 | public void setShutdownWaitMilliseconds(int shutdownWaitMilliseconds) { 118 | if (shutdownWaitMilliseconds < 0) { 119 | throw new IllegalArgumentException("cannot set negative timeout:" + shutdownWaitMilliseconds); 120 | } 121 | this.shutdownWaitMilliseconds = shutdownWaitMilliseconds; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/PoolExhaustedException.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | /** 4 | * When the pool has been exhausted, getObject(false) will throw this exception 5 | * if no object is available within PoolConfig.maxWaitMilliseconds. 6 | */ 7 | public class PoolExhaustedException extends RuntimeException { 8 | 9 | public PoolExhaustedException() { 10 | super("Cannot get an object, the pool is exhausted."); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/PoolInvalidObjectException.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | /** 4 | * This exception is to be thrown by ObjectPool.borrowObject(), if the object retrieved from the pool is invalid 5 | * (cannot be validated by factory.validate(T obj)) 6 | */ 7 | public class PoolInvalidObjectException extends RuntimeException { 8 | public PoolInvalidObjectException() { 9 | super("Cannot find an object that can be validated by ObjectFactory.validate()"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/danielw/fop/Poolable.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | /** 4 | * @author Daniel 5 | * AutoCloseable.close() is not idemponent, so don't close it multiple times! 6 | */ 7 | public class Poolable implements AutoCloseable { 8 | 9 | private final T object; 10 | private ObjectPool pool; 11 | private final int partition; 12 | private long lastAccessTs; 13 | 14 | /** 15 | * the number of partitions has an effect on the concurrency, please test with your real production environments to find the best settings 16 | */ 17 | public Poolable(T t, ObjectPool pool, int partition) { 18 | this.object = t; 19 | this.pool = pool; 20 | this.partition = partition; 21 | this.lastAccessTs = System.currentTimeMillis(); 22 | } 23 | 24 | public T getObject() { 25 | return object; 26 | } 27 | 28 | public ObjectPool getPool() { 29 | return pool; 30 | } 31 | 32 | public int getPartition() { 33 | return partition; 34 | } 35 | 36 | public void returnObject() { 37 | pool.returnObject(this); 38 | } 39 | 40 | public long getLastAccessTs() { 41 | return lastAccessTs; 42 | } 43 | 44 | public void setLastAccessTs(long lastAccessTs) { 45 | this.lastAccessTs = lastAccessTs; 46 | } 47 | 48 | /** 49 | * This method is not idemponent, don't call it twice, which will return the object twice to the pool and cause severe problems. 50 | */ 51 | @Override 52 | public void close() { 53 | this.returnObject(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/TestObjectPool.java: -------------------------------------------------------------------------------- 1 | import cn.danielw.fop.ObjectFactory; 2 | import cn.danielw.fop.ObjectPool; 3 | import cn.danielw.fop.PoolConfig; 4 | import cn.danielw.fop.Poolable; 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | 14 | // create a test pool 15 | public class TestObjectPool { 16 | 17 | public ObjectPool init(double scavengeRatio) { 18 | Logger.getLogger("").getHandlers()[0].setLevel(Level.ALL); 19 | Logger.getLogger("").setLevel(Level.ALL); 20 | PoolConfig config = new PoolConfig(); 21 | config.setPartitionSize(2).setMinSize(2).setMaxSize(20).setMaxIdleMilliseconds(5000). 22 | setMaxWaitMilliseconds(100).setScavengeIntervalMilliseconds(5000).setScavengeRatio(scavengeRatio); 23 | 24 | ObjectFactory factory = new ObjectFactory() { 25 | @Override 26 | public StringBuilder create() { 27 | return new StringBuilder(); 28 | } 29 | 30 | @Override 31 | public void destroy(StringBuilder o) { 32 | } 33 | 34 | @Override 35 | public boolean validate(StringBuilder o) { 36 | return true; 37 | } 38 | }; 39 | return new ObjectPool<>(config, factory); 40 | } 41 | 42 | @Test 43 | public void testSimple() { 44 | ObjectPool pool = init(1.0); 45 | for (int i = 0; i < 100; i++) { 46 | try (Poolable obj = pool.borrowObject()) { 47 | obj.getObject().append("x"); 48 | } 49 | } 50 | System.out.println("pool size:" + pool.getSize()); 51 | assertEquals(4, pool.getSize()); 52 | } 53 | 54 | @Test 55 | public void testShrink() throws InterruptedException { 56 | final ObjectPool pool = init(1.0); 57 | List> borrowed = new ArrayList<>(); 58 | for (int i = 0; i < 10; i++) { 59 | System.out.println("test borrow"); 60 | Poolable obj = pool.borrowObject(); 61 | obj.getObject().append("x"); 62 | borrowed.add(obj); 63 | } 64 | System.out.println("pool size:" + pool.getSize()); 65 | 66 | for (Poolable obj : borrowed) { 67 | System.out.println("test return"); 68 | pool.returnObject(obj); 69 | } 70 | assertEquals(12, pool.getSize()); 71 | System.out.println("pool size:" + pool.getSize()); 72 | 73 | Thread.sleep(20000); //NOSONAR 74 | assertEquals(4, pool.getSize()); 75 | System.out.println("scavenged, pool size=" + pool.getSize()); 76 | 77 | // test return after shutdown 78 | Thread testThread = new Thread(() -> { 79 | Poolable obj = pool.borrowObject(); 80 | System.out.println("pool size:" + pool.getSize()); 81 | try { Thread.sleep(10000); } catch (InterruptedException ignored) { } 82 | pool.returnObject(obj); 83 | System.out.println("pool size:" + pool.getSize()); 84 | }); 85 | testThread.start(); 86 | testThread.join(); 87 | int removed = pool.shutdown(); // this will block 9 seconds 88 | assertEquals(4, removed); 89 | System.out.println("All done"); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/cn/danielw/fop/PoolConfigTest.java: -------------------------------------------------------------------------------- 1 | package cn.danielw.fop; 2 | 3 | import org.junit.Test; 4 | 5 | import static java.lang.Math.abs; 6 | import static org.junit.Assert.*; 7 | 8 | public class PoolConfigTest { 9 | 10 | @Test 11 | public void testMaxWait() { 12 | PoolConfig c = new PoolConfig(); 13 | assertEquals(5000, c.getMaxWaitMilliseconds()); // default value 14 | c.setMaxWaitMilliseconds(10000); 15 | assertEquals(10000, c.getMaxWaitMilliseconds()); 16 | assertThrows(IllegalArgumentException.class, () -> c.setMaxWaitMilliseconds(0)); 17 | assertThrows(IllegalArgumentException.class, () -> c.setMaxWaitMilliseconds(-10)); 18 | } 19 | 20 | @Test 21 | public void testScavenge() { 22 | PoolConfig c = new PoolConfig(); 23 | assertEquals(1000 * 60 * 2, c.getScavengeIntervalMilliseconds()); // default value 24 | c.setScavengeIntervalMilliseconds(0); 25 | assertEquals(0, c.getScavengeIntervalMilliseconds()); 26 | c.setScavengeIntervalMilliseconds(10000); 27 | assertEquals(10000, c.getScavengeIntervalMilliseconds()); 28 | assertThrows(IllegalArgumentException.class, () -> c.setScavengeIntervalMilliseconds(10)); 29 | 30 | assertEquals(c.getScavengeRatio(), 0.5, 0.00000001); 31 | c.setScavengeRatio(0.85); 32 | assertEquals(c.getScavengeRatio(), 0.85, 0.00000001); 33 | c.setScavengeRatio(1); 34 | assertEquals(c.getScavengeRatio(), 1, 0.00000001); 35 | assertThrows(IllegalArgumentException.class, () -> c.setScavengeRatio(-0.1)); 36 | assertThrows(IllegalArgumentException.class, () -> c.setScavengeRatio(0)); 37 | assertThrows(IllegalArgumentException.class, () -> c.setScavengeRatio(1.1)); 38 | } 39 | 40 | @Test 41 | public void testShutdown() { 42 | PoolConfig c = new PoolConfig(); 43 | assertEquals(1000 * 30, c.getShutdownWaitMilliseconds()); 44 | c.setShutdownWaitMilliseconds(1000 * 40); 45 | assertEquals(1000 * 40, c.getShutdownWaitMilliseconds()); 46 | assertThrows(IllegalArgumentException.class, () -> c.setShutdownWaitMilliseconds(-1000)); 47 | } 48 | 49 | } 50 | --------------------------------------------------------------------------------