├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── pom.xml └── src ├── main └── java │ └── uk │ └── co │ └── boundedbuffer │ ├── AbstractBlockingQueue.java │ ├── ConcurrentBlockingByteQueue.java │ ├── ConcurrentBlockingDoubleQueue.java │ ├── ConcurrentBlockingFloatQueue.java │ ├── ConcurrentBlockingIntQueue.java │ ├── ConcurrentBlockingLongQueue.java │ ├── ConcurrentBlockingObjectQueue.java │ └── ConcurrentBlockingShortQueue.java └── test ├── java └── uk │ └── co │ └── boundedbuffer │ ├── ArrayBlockingQueueTest.java │ ├── BlockingQueueTest.java │ ├── ConcurrentBlockingObjectQueueTest.java │ └── JSR166TestCase.java └── resources └── performance-comparison.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | *.iml 4 | /.settings/ 5 | /.classpath 6 | /.project 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | 5 | after_success: 6 | - mvn clean cobertura:cobertura org.eluder.coveralls:coveralls-maven-plugin:report 7 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Faster than ArrayBlockingQueue 2 | 3 | ![PerformanceComparison](https://raw.github.com/RobAustin/low-latency-primitive-concurrent-queues/master/src/test/resources/performance-comparison.png) 4 | 5 | ### Aim 6 | 7 | The aim is to be extremely low latency with near zero [GC](http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html) overhead. 8 | 9 | 10 | An example of how to use one of our low latency bounded queues. 11 | 12 | ``` 13 | 14 | // writer thread 15 | Executors.newSingleThreadExecutor().execute(new Runnable() { 16 | @Override 17 | public void run() { 18 | queue.add(1); 19 | } 20 | }); 21 | 22 | // reader thread 23 | Executors.newSingleThreadExecutor().execute(new Runnable() { 24 | @Override 25 | public void run() { 26 | final int value = queue.take(); 27 | } 28 | }); 29 | 30 | 31 | ``` 32 | 33 | ### Maven Central 34 | We are hosted at [Maven Central] (http://search.maven.org), one of the quickest ways to get up and running is to add this [Maven](http://maven.apache.org/what-is-maven.html) dependency to your pom file : 35 | 36 | ``` 37 | 38 | uk.co.boundedbuffer 39 | low-latency-primitive-concurrent-queues 40 | 1.0.1 41 | 42 | ``` 43 | 44 | ### JavaDoc 45 | Having trouble ? Check out our documentation at [JavaDoc] (http://robaustin.github.io/low-latency-primitive-concurrent-queues/apidocs/) 46 | 47 | ### Is this Queue Thread Safe ? 48 | 49 | Yes - it's thread safe, but you are limited to using just two threads per queue instance, One producer thread and a consumer thread. 50 | 51 | ### Why am I limited to only using just two threads ? 52 | 53 | The queues take advantage of the Unsafe.putOrderedX(), which provides non-blocking code with guaranteed writes. These writes will not be re-ordered by instruction reordering, they use a faster store-store barrier, rather than the the slower store-load barrier ( which is used when doing a volatile write ). One of the trade offs with this improved performance is the visibility of the reads and writes between cores. 54 | 55 | ### Licence 56 | [Apache v2](http://www.apache.org/licenses/LICENSE-2.0.html) 57 | 58 | ### Contributors 59 | Contributors are extremely welcome, just fork this project, make your changes, and we'd be happy to review your pull-request. 60 | 61 | ### Support or Contact 62 | Having Problems ? Contact support@boundedbuffer.com and we’ll help you sort it out. 63 | 64 | ### Blog 65 | If you are interest in low latency java, see my blog at [http://robsjava.blogspot.co.uk] (http://robsjava.blogspot.co.uk) 66 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | 20 | uk.co.boundedbuffer 21 | low-latency-primitive-concurrent-queues 22 | 1.0.14-SNAPSHOT 23 | 24 | jar 25 | Low Latency Primitive Concurrent Queues 26 | 27 | 28 | An low latency, lock free, primitive bounded blocking queue backed by an int[]. 29 | This class mimics the interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, 30 | however works with primitive ints rather than objects, so is unable to actually implement the 31 | BlockingQueue. 32 | 33 | This class takes advantage of the Unsafe.putOrderedObject, which allows us to create non-blocking code 34 | with 35 | guaranteed writes. 36 | These writes will not be re-orderd by instruction reordering. Under the covers it uses the faster 37 | store-store 38 | barrier, rather than the the slower store-load barrier, which is used when doing a volatile write. 39 | One of the trade off with this improved performance is we are limited to a single producer, single 40 | consumer. 41 | 42 | 43 | 44 | 45 | scm:git:git@github.com:BoundedBuffer/low-latency-primitive-concurrent-queues 46 | scm:git:git@github.com:BoundedBuffer/low-latency-primitive-concurrent-queues 47 | 48 | scm:git:@github.com:BoundedBuffer/low-latency-primitive-concurrent-queues 49 | low-latency-primitive-concurrent-queues-1.0.9 50 | 51 | 52 | 53 | 1.6 54 | 55 | 56 | 57 | 58 | org.mockito 59 | mockito-core 60 | 1.9.5 61 | test 62 | 63 | 64 | 65 | junit 66 | junit 67 | 4.13.1 68 | test 69 | 70 | 71 | 72 | com.sun.japex 73 | japex-maven-plugin 74 | 1.2.4 75 | test 76 | 77 | 78 | 79 | com.sun.japex 80 | japex 81 | 1.2.4 82 | test 83 | 84 | 85 | 86 | 87 | 88 | 89 | The Apache Software License, Version 2.0 90 | http://www.apache.org/licenses/LICENSE-2.0.txt 91 | repo 92 | A business-friendly OSS license 93 | 94 | 95 | 96 | 97 | 98 | Rob Austin 99 | rob.austin@boundedbuffer.com 100 | 101 | 102 | 103 | 104 | 105 | 106 | sonatype-nexus-staging 107 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 108 | 109 | 110 | sonatype-nexus-snapshots 111 | https://oss.sonatype.org/content/repositories/snapshots/ 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | true 120 | 121 | Snapshot Repository 122 | Snapshot Repository 123 | https://oss.sonatype.org/content/repositories/snapshots 124 | 125 | 126 | 127 | true 128 | 129 | sonatype-nexus-staging 130 | https://oss.sonatype.org/service/local/staging/deploy/maven2 131 | 132 | 133 | 134 | false 135 | 136 | central 137 | Central Repository 138 | http://repo.maven.apache.org/maven2 139 | 140 | 141 | 142 | 143 | 144 | 145 | never 146 | 147 | 148 | false 149 | 150 | central 151 | Central Repository 152 | http://repo.maven.apache.org/maven2 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | org.apache.maven.plugins 163 | maven-dependency-plugin 164 | 2.1 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-release-plugin 169 | 2.5.1 170 | 171 | 172 | org.apache.maven.plugins 173 | maven-gpg-plugin 174 | 1.4 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-javadoc-plugin 179 | 2.9.1 180 | 181 | 182 | org.apache.maven.plugins 183 | maven-source-plugin 184 | 3.0.1 185 | 186 | 187 | 188 | jar 189 | 190 | 191 | 192 | 193 | 194 | org.apache.maven.plugins 195 | maven-deploy-plugin 196 | 2.8.1 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-compiler-plugin 201 | 3.5.1 202 | 203 | ${java.level} 204 | ${java.level} 205 | 206 | 207 | 208 | org.eluder.coveralls 209 | coveralls-maven-plugin 210 | 3.0.0 211 | 212 | UTF-8 213 | 214 | 215 | 216 | org.codehaus.mojo 217 | cobertura-maven-plugin 218 | 2.6 219 | 220 | 221 | html 222 | xml 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | maven-release-plugin 232 | 233 | 234 | org.apache.maven.scm 235 | maven-scm-provider-gitexe 236 | 1.9.1 237 | 238 | 239 | 240 | true 241 | forked-path 242 | false 243 | 244 | 245 | 246 | org.apache.maven.plugins 247 | maven-clean-plugin 248 | 2.4.1 249 | 250 | 251 | default-clean 252 | clean 253 | 254 | clean 255 | 256 | 257 | 258 | 259 | 260 | org.apache.maven.plugins 261 | maven-install-plugin 262 | 2.3.1 263 | 264 | 265 | default-install 266 | install 267 | 268 | install 269 | 270 | 271 | 272 | 273 | 274 | org.apache.maven.plugins 275 | maven-deploy-plugin 276 | 277 | 278 | default-deploy 279 | deploy 280 | 281 | deploy 282 | 283 | 284 | 285 | 286 | 287 | org.apache.maven.plugins 288 | maven-source-plugin 289 | 290 | 291 | 292 | 293 | 294 | 328 | 329 | 330 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/AbstractBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.lang.reflect.Field; 6 | import java.util.NoSuchElementException; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.concurrent.TimeoutException; 9 | 10 | /** 11 | * Copyright 2014 Rob Austin 12 | *

13 | * Licensed under the Apache License, Version 2.0 (the "License"); 14 | * you may not use this file except in compliance with the License. 15 | * You may obtain a copy of the License at 16 | *

17 | * http://www.apache.org/licenses/LICENSE-2.0 18 | *

19 | * Unless required by applicable law or agreed to in writing, software 20 | * distributed under the License is distributed on an "AS IS" BASIS, 21 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | * See the License for the specific language governing permissions and 23 | * limitations under the License. 24 | * 25 | * @author Rob Austin 26 | * @since 1.1 27 | */ 28 | class AbstractBlockingQueue { 29 | 30 | private static final long READ_LOCATION_OFFSET; 31 | private static final long WRITE_LOCATION_OFFSET; 32 | private static final Unsafe unsafe; 33 | 34 | static { 35 | try { 36 | final Field field = Unsafe.class.getDeclaredField("theUnsafe"); 37 | field.setAccessible(true); 38 | unsafe = (Unsafe) field.get(null); 39 | READ_LOCATION_OFFSET = unsafe.objectFieldOffset 40 | (AbstractBlockingQueue.class.getDeclaredField("readLocation")); 41 | WRITE_LOCATION_OFFSET = unsafe.objectFieldOffset 42 | (AbstractBlockingQueue.class.getDeclaredField("writeLocation")); 43 | } catch (Exception e) { 44 | throw new AssertionError(e); 45 | } 46 | } 47 | 48 | final int capacity; 49 | 50 | // only set and read by the producer thread, ( that the thread that's calling put(), offer() or add() ) 51 | int producerWriteLocation; 52 | 53 | // only set and read by the comumer thread, ( that the thread that's calling get(), poll() or peek() ) 54 | int consumerReadLocation; 55 | 56 | // we set volatiles here, for the writes we use putOrderedInt ( as this is quicker ), 57 | // but for the read of a volatile there is no performance benefit un using getOrderedInt. 58 | volatile int readLocation; 59 | volatile int writeLocation; 60 | 61 | /** 62 | * @param capacity Creates an BlockingQueue with the given (fixed) capacity 63 | */ 64 | public AbstractBlockingQueue(int capacity) { 65 | if (capacity == 0) 66 | throw new IllegalArgumentException(); 67 | this.capacity = capacity + 1; 68 | } 69 | 70 | 71 | /** 72 | * Creates an BlockingQueue with the default capacity of 1024 73 | */ 74 | public AbstractBlockingQueue() { 75 | this.capacity = 1024; 76 | } 77 | 78 | 79 | void setWriteLocation(int nextWriteLocation) { 80 | 81 | 82 | // putOrderedInt wont immediately make the updates available, even on this thread, so will update the field so the change is immediately visible to, at least this thread. ( note the field is non volatile ) 83 | this.producerWriteLocation = nextWriteLocation; 84 | 85 | // the line below, is where the write memory barrier occurs, 86 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 87 | 88 | // write back the next write location 89 | unsafe.putOrderedInt(this, WRITE_LOCATION_OFFSET, nextWriteLocation); 90 | } 91 | 92 | void setReadLocation(int nextReadLocation) { 93 | 94 | // putOrderedInt wont immediately make the updates available, even on this thread, so will update the field so the change is immediately visible to, at least this thread. ( note the field is non volatile ) 95 | this.consumerReadLocation = nextReadLocation; 96 | 97 | // the write memory barrier will occur here, as we are storing the nextReadLocation 98 | unsafe.putOrderedInt(this, READ_LOCATION_OFFSET, nextReadLocation); 99 | } 100 | 101 | /** 102 | * currently implement as a spin lock 103 | */ 104 | private void blockAtTake() { 105 | } 106 | 107 | /** 108 | * currently implement as a spin lock 109 | * 110 | * @param timeoutAt returns false if the timeoutAt time is reached 111 | */ 112 | private boolean blockAtTake(long timeoutAt) { 113 | return timeoutAt > System.nanoTime(); 114 | } 115 | 116 | /** 117 | * currently implement as a spin lock 118 | */ 119 | private void blockAtAdd() { 120 | } 121 | 122 | /** 123 | * currently implement as a spin lock 124 | * 125 | * @param timeoutAt returns false if the timeoutAt time is reached 126 | */ 127 | boolean blockAtAdd(long timeoutAt) { 128 | return timeoutAt > System.nanoTime(); 129 | } 130 | 131 | 132 | /** 133 | * This method is not thread safe it therefore only provides and approximation of the size, 134 | * the size will be corrected if nothing was added or removed from the queue at the time it was called 135 | * 136 | * @return an approximation of the size 137 | */ 138 | public int size() { 139 | int read = readLocation; 140 | int write = writeLocation; 141 | 142 | if (write < read) 143 | write += capacity; 144 | 145 | return write - read; 146 | 147 | } 148 | 149 | 150 | /** 151 | * The items will be cleared correctly only if nothing was added or removed from the queue at the time it was called 152 | * 153 | * @return an approximation of the size 154 | */ 155 | public void clear() { 156 | readLocation = writeLocation; 157 | } 158 | 159 | 160 | /** 161 | * This method does not lock, it therefore only provides and approximation of isEmpty(), 162 | * it will be correct, if nothing was added or removed from the queue at the time it was called. 163 | * 164 | * @return an approximation of isEmpty() 165 | */ 166 | public boolean isEmpty() { 167 | return readLocation == writeLocation; 168 | } 169 | 170 | /** 171 | * @param writeLocation the current write location 172 | * @return the next write location 173 | */ 174 | int getNextWriteLocationThrowIfFull(int writeLocation) throws IllegalStateException { 175 | 176 | // we want to minimize the number of volatile reads, so we read the writeLocation just once. 177 | 178 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 179 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 180 | 181 | if (nextWriteLocation == capacity) { 182 | 183 | if (readLocation == 0) 184 | throw new IllegalStateException("queue is full"); 185 | 186 | } else if (nextWriteLocation == readLocation) 187 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 188 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 189 | throw new IllegalStateException("queue is full"); 190 | 191 | return nextWriteLocation; 192 | } 193 | 194 | 195 | /** 196 | * @param writeLocation the current write location 197 | * @return the next write location 198 | */ 199 | int blockForWriteSpaceInterruptibly(int writeLocation) throws InterruptedException { 200 | 201 | // we want to minimize the number of volatile reads, so we read the writeLocation just once. 202 | 203 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 204 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 205 | 206 | if (nextWriteLocation == capacity) 207 | 208 | while (readLocation == 0) { 209 | 210 | if (Thread.interrupted()) 211 | throw new InterruptedException(); 212 | 213 | 214 | // // this condition handles the case where writer has caught up with the read, 215 | // we will wait for a read, ( which will cause a change on the read location ) 216 | blockAtAdd(); 217 | 218 | } 219 | else 220 | 221 | 222 | while (nextWriteLocation == readLocation) { 223 | 224 | if (Thread.interrupted()) 225 | throw new InterruptedException(); 226 | 227 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 228 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 229 | blockAtAdd(); 230 | 231 | } 232 | return nextWriteLocation; 233 | } 234 | 235 | /** 236 | * @param writeLocation the current write location 237 | * @return the next write location 238 | */ 239 | int blockForWriteSpace(int writeLocation) { 240 | 241 | // we want to minimize the number of volatile reads, so we read the writeLocation just once. 242 | 243 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 244 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 245 | 246 | if (nextWriteLocation == capacity) 247 | 248 | while (readLocation == 0) 249 | // // this condition handles the case where writer has caught up with the read, 250 | // we will wait for a read, ( which will cause a change on the read location ) 251 | blockAtAdd(); 252 | 253 | else 254 | 255 | 256 | while (nextWriteLocation == readLocation) 257 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 258 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 259 | blockAtAdd(); 260 | 261 | return nextWriteLocation; 262 | } 263 | 264 | /** 265 | * @param timeout how long to wait before giving up, in units of 266 | * unit 267 | * @param unit a TimeUnit determining how to interpret the 268 | * timeout parameter 269 | * @param readLocation we want to minimize the number of volatile reads, so we read the readLocation just once and get it passed in 270 | * @return 271 | * @throws TimeoutException 272 | */ 273 | int blockForReadSpace(long timeout, TimeUnit unit, int readLocation) throws TimeoutException { 274 | 275 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 276 | final int nextReadLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 277 | 278 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 279 | 280 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka size()=0) 281 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 282 | 283 | while (writeLocation == readLocation) 284 | if (!blockAtTake(timeoutAt)) 285 | throw new TimeoutException(); 286 | 287 | return nextReadLocation; 288 | } 289 | 290 | 291 | /** 292 | * /** 293 | * 294 | * @param readLocation we want to minimize the number of volatile reads, so we read the readLocation just once, and pass it in 295 | * @return 296 | */ 297 | int blockForReadSpace(int readLocation) { 298 | 299 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 300 | final int nextReadLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 301 | 302 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka size()=0) 303 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 304 | while (writeLocation == readLocation) 305 | blockAtTake(); 306 | 307 | return nextReadLocation; 308 | } 309 | 310 | 311 | /** 312 | * /** 313 | * 314 | * @param readLocation we want to minimize the number of volatile reads, so we read the readLocation just once, and pass it in 315 | * @return 316 | */ 317 | int blockForReadSpaceThrowNoSuchElementException(int readLocation) { 318 | 319 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 320 | final int nextReadLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 321 | 322 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka size()=0) 323 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 324 | while (writeLocation == readLocation) 325 | throw new NoSuchElementException(); 326 | 327 | return nextReadLocation; 328 | } 329 | 330 | 331 | /** 332 | * Returns the number of additional elements that this queue can ideally 333 | * (in the absence of memory or resource constraints) accept without 334 | * blocking, or Integer.MAX_VALUE if there is no intrinsic 335 | * limit. 336 | *

337 | *

Note that you cannot always tell if an attempt to insert 338 | * an element will succeed by inspecting remainingCapacity 339 | * because it may be the case that another thread is about to 340 | * insert or remove an element. 341 | * 342 | * @return the remaining capacity 343 | */ 344 | public int remainingCapacity() { 345 | 346 | int readLocation = this.readLocation; 347 | int writeLocation = this.writeLocation; 348 | 349 | if (writeLocation < readLocation) 350 | writeLocation += capacity; 351 | 352 | 353 | return (capacity - 1) - (writeLocation - readLocation); 354 | } 355 | 356 | 357 | } 358 | 359 | 360 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/ConcurrentBlockingByteQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | 6 | /** 7 | * A low latency, lock free, primitive bounded blocking queue backed by an byte[]. 8 | * This class mimics the interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, 9 | * however works with primitive bytes rather than {@link Object}s, so is unable to actually implement the BlockingQueue . 10 | *

11 | * This class takes advantage of the Unsafe.putOrderedInt, which allows us to create non-blocking code with guaranteed writes. 12 | * These writes will not be re-ordered by instruction reordering. Under the covers it uses the faster store-store barrier, rather than the the slower store-load barrier, which is used when doing a volatile write. 13 | * One of the trade off with this improved performance is we are limited to a single producer, single consumer. 14 | * For further information on this see, the blog post A Faster Volatile by Rob Austin. 15 | *

16 | *

17 | *

18 | * This queue orders elements FIFO (first-in-first-out). The 19 | * head of the queue is that element that has been on the 20 | * queue the longest time. The tail of the queue is that 21 | * element that has been on the queue the shortest time. New elements 22 | * are inserted at the tail of the queue, and the queue retrieval 23 | * operations obtain elements at the head of the queue. 24 | *

25 | *

This is a classic "bounded buffer", in which a 26 | * fixed-sized array holds elements inserted by producers and 27 | * extracted by consumers. Once created, the capacity cannot be 28 | * changed. Attempts to {@link #put put(e)} an element into a full queue 29 | * will result in the operation blocking; attempts to {@link #take take()} an 30 | * element from an empty queue will similarly block. 31 | *

32 | *

Due to the lock free nature of its implementation, ordering works on a first come first served basis.

33 | * Methods come in four forms, with different ways 34 | * of handling operations that cannot be satisfied immediately, but may be 35 | * satisfied at some point in the future: 36 | * one throws an exception, the second returns a special value (either 37 | * null or false, depending on the operation), the third 38 | * blocks the current thread indefinitely until the operation can succeed, 39 | * and the fourth blocks for only a given maximum time limit before giving 40 | * up. These methods are summarized in the following table: 41 | * 42 | * 43 | * 44 | * 45 | * 46 | * 47 | * 48 | * 49 | * 50 | * 51 | * 52 | * 53 | * 54 | * 55 | * 56 | * 57 | * 58 | * 59 | * 60 | * 61 | * 62 | * 63 | * 64 | * 65 | * 66 | * 67 | * 68 | * > 69 | * 70 | *
Throws exceptionSpecial valueBlocksTimes out
Insert{@link #add add(e)}{@link #offer offer(e)}{@link #put put(e)}{@link #offer(byte, long, java.util.concurrent.TimeUnit) offer(e, time, unit)}
Remove{@link #poll poll()}not applicable{@link #take long()}{@link #poll(long, java.util.concurrent.TimeUnit) poll(time, unit)}
Examinenot applicablenot applicablenot applicable{@link #peek(long, java.util.concurrent.TimeUnit) peek(time, unit)}
71 | *

72 | *

73 | *

A uk.co.boundedbuffer.ConcurrentBlockingByteQueue is capacity bounded. At any given 74 | * time it may have a remainingCapacity beyond which no 75 | * additional elements can be put without blocking. 76 | *

77 | *

It is not possible to remove an arbitrary element from a queue using 78 | * remove(x). As this operation would not performed very efficiently. 79 | *

80 | *

All of uk.co.boundedbuffer.ConcurrentBlockingByteQueue methods are thread-safe when used with a single producer and single consumer, internal atomicity 81 | * is achieved using lock free strategies, such as sping locks. 82 | *

83 | *

Like a BlockingQueue, the uk.co.boundedbuffer.ConcurrentBlockingByteQueue does not intrinsically support 84 | * any kind of "close" or "shutdown" operation to 85 | * indicate that no more items will be added. The needs and usage of 86 | * such features tend to be implementation-dependent. For example, a 87 | * common tactic is for producers to insert special 88 | * end-of-stream or poison objects, that are 89 | * interpreted accordingly when taken by consumers. 90 | *

91 | *

92 | * Usage example, based on a typical producer-consumer scenario. 93 | * Note that a BlockingQueue can ONLY be used with a single 94 | * producers and single consumer thead. 95 | *

 96 |  *
 97 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 98 |  *
 99 |  *  public void run() {
100 |  *       queue.add(1);
101 |  *       }
102 |  *  });
103 |  *
104 |  *
105 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
106 |  *
107 |  *  public void run() {
108 |  *           final int value = queue.take();
109 |  *       }
110 |  *  });
111 |  * 
112 | *

113 | *

Memory consistency effects: As with other concurrent 114 | * collections, actions in a thread prior to placing an object into a 115 | * {@code BlockingQueue} 116 | * happen-before 117 | * actions subsequent to the access or removal of that element from 118 | * the {@code BlockingQueue} in another thread. 119 | *

120 | *

121 | *

122 | * Copyright 2014 Rob Austin 123 | *

124 | * Licensed under the Apache License, Version 2.0 (the "License"); 125 | * you may not use this file except in compliance with the License. 126 | * You may obtain a copy of the License at 127 | *

128 | * http://www.apache.org/licenses/LICENSE-2.0 129 | *

130 | * Unless required by applicable law or agreed to in writing, software 131 | * distributed under the License is distributed on an "AS IS" BASIS, 132 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133 | * See the License for the specific language governing permissions and 134 | * limitations under the License. 135 | * 136 | * @author Rob Austin 137 | * @since 1.1 138 | */ 139 | public class ConcurrentBlockingByteQueue extends AbstractBlockingQueue { 140 | 141 | // intentionally not volatile, as we are carefully ensuring that the memory barriers are controlled below by other objects 142 | private final byte[] data = new byte[capacity]; 143 | 144 | 145 | /** 146 | * Creates an BlockingQueue with the default capacity of 1024 147 | */ 148 | public ConcurrentBlockingByteQueue() { 149 | 150 | } 151 | 152 | /** 153 | * @param size Creates an BlockingQueue with the given (fixed) capacity 154 | */ 155 | public ConcurrentBlockingByteQueue(int size) { 156 | super(size); 157 | } 158 | 159 | 160 | /** 161 | * Inserts the specified element into this queue if it is possible to do 162 | * so immediately without violating capacity restrictions, returning 163 | * true upon success and throwing an 164 | * IllegalStateException if no space is currently available. 165 | * When using a capacity-restricted queue, it is generally preferable to 166 | * use {@link #offer(byte) offer}. 167 | * 168 | * @param value the element to add 169 | * @return true (as specified by {@link java.util.Collection#add}) 170 | * @throws IllegalStateException if the element cannot be added at this 171 | * time due to capacity restrictions 172 | * @throws ClassCastException if the class of the specified element 173 | * prevents it from being added to this queue 174 | * @throws NullPointerException if the specified element is null 175 | * @throws IllegalArgumentException if some property of the specified 176 | * element prevents it from being added to this queue 177 | */ 178 | public boolean add(byte value) { 179 | 180 | // volatile read 181 | final int writeLocation = this.writeLocation; 182 | 183 | final int nextWriteLocation = getNextWriteLocationThrowIfFull(writeLocation); 184 | 185 | // purposely not volatile 186 | data[writeLocation] = value; 187 | 188 | setWriteLocation(nextWriteLocation); 189 | return true; 190 | } 191 | 192 | /** 193 | * Retrieves and removes the head of this queue, waiting if necessary 194 | * until an element becomes available. 195 | *

196 | * the reads must always occur on the same thread 197 | * 198 | * @return the head of this queue 199 | * @throws InterruptedException if interrupted while waiting 200 | */ 201 | public byte take() { 202 | 203 | // volatile read 204 | final int readLocation = this.readLocation; 205 | 206 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 207 | final int nextReadLocation = blockForReadSpace(readLocation); 208 | 209 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 210 | final byte value = data[readLocation]; 211 | 212 | setReadLocation(nextReadLocation); 213 | 214 | return value; 215 | 216 | } 217 | 218 | /** 219 | * Retrieves, but does not remove, the head of this queue. 220 | * 221 | * @param timeout how long to wait before giving up, in units of 222 | * unit 223 | * @param unit a TimeUnit determining how to interpret the 224 | * timeout parameter 225 | * @return the head of this queue 226 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 227 | */ 228 | 229 | public byte peek(long timeout, TimeUnit unit) 230 | throws InterruptedException, TimeoutException { 231 | 232 | final int readLocation = this.readLocation; 233 | 234 | blockForReadSpace(timeout, unit, readLocation); 235 | 236 | // purposely not volatile as the read memory barrier occurred above when we read ' this.readLocation' 237 | return data[readLocation]; 238 | 239 | } 240 | 241 | /** 242 | * Inserts the specified element into this queue if it is possible to do 243 | * so immediately without violating capacity restrictions, returning 244 | * true upon success and false if no space is currently 245 | * available. When using a capacity-restricted queue, this method is 246 | * generally preferable to {@link #add}, which can fail to insert an 247 | * element only by throwing an exception. 248 | * 249 | * @param value the element to add 250 | * @return true if the element was added to this queue, else 251 | * false 252 | * @throws NullPointerException if the specified element is null 253 | * @throws IllegalArgumentException if some property of the specified 254 | * element prevents it from being added to this queue 255 | */ 256 | public boolean offer(byte value) { 257 | 258 | // we want to minimize the number of volatile reads, so we read the writeLocation just once. 259 | final int writeLocation = this.writeLocation; 260 | 261 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 262 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 263 | 264 | if (nextWriteLocation == capacity - 1) { 265 | 266 | if (readLocation == 0) 267 | return false; 268 | 269 | } else if (nextWriteLocation + 1 == readLocation) 270 | return false; 271 | 272 | // purposely not volatile see the comment below 273 | data[writeLocation] = value; 274 | 275 | setWriteLocation(nextWriteLocation); 276 | return true; 277 | } 278 | 279 | /** 280 | * Inserts the specified element into this queue, waiting if necessary 281 | * for space to become available. 282 | * 283 | * @param value the element to add 284 | * @throws InterruptedException if interrupted while waiting 285 | * @throws IllegalArgumentException if some property of the specified 286 | * element prevents it from being added to this queue 287 | */ 288 | public void put(byte value) throws InterruptedException { 289 | 290 | final int writeLocation1 = this.writeLocation; 291 | final int nextWriteLocation = blockForWriteSpace(writeLocation1); 292 | 293 | // purposely not volatile see the comment below 294 | data[writeLocation1] = value; 295 | 296 | setWriteLocation(nextWriteLocation); 297 | } 298 | 299 | /** 300 | * Inserts the specified element into this queue, waiting up to the 301 | * specified wait time if necessary for space to become available. 302 | * 303 | * @param value the element to add 304 | * @param timeout how long to wait before giving up, in units of 305 | * unit 306 | * @param unit a TimeUnit determining how to interpret the 307 | * timeout parameter 308 | * @return true if successful, or false if 309 | * the specified waiting time elapses before space is available 310 | * @throws InterruptedException if interrupted while waiting 311 | * @throws ClassCastException if the class of the specified element 312 | * prevents it from being added to this queue 313 | * @throws NullPointerException if the specified element is null 314 | * @throws IllegalArgumentException if some property of the specified 315 | * element prevents it from being added to this queue 316 | */ 317 | public boolean offer(byte value, long timeout, TimeUnit unit) 318 | throws InterruptedException { 319 | 320 | // we want to minimize the number of volatile reads, so we read the writeLocation just once. 321 | final int writeLocation = this.writeLocation; 322 | 323 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 324 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 325 | 326 | 327 | if (nextWriteLocation == capacity - 1) { 328 | 329 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 330 | 331 | while (readLocation == 0) 332 | // this condition handles the case where writer has caught up with the read, 333 | // we will wait for a read, ( which will cause a change on the read location ) 334 | { 335 | if (!blockAtAdd(timeoutAt)) 336 | return false; 337 | } 338 | 339 | } else { 340 | 341 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 342 | 343 | while (nextWriteLocation == readLocation) 344 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 345 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 346 | { 347 | if (!blockAtAdd(timeoutAt)) 348 | return false; 349 | } 350 | } 351 | 352 | // purposely not volatile see the comment below 353 | data[writeLocation] = value; 354 | 355 | // the line below, is where the write memory barrier occurs, 356 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 357 | setWriteLocation(nextWriteLocation); 358 | 359 | return true; 360 | } 361 | 362 | /** 363 | * Retrieves and removes the head of this queue, waiting up to the 364 | * specified wait time if necessary for an element to become available. 365 | * 366 | * @param timeout how long to wait before giving up, in units of 367 | * unit 368 | * @param unit a TimeUnit determining how to interpret the 369 | * timeout parameter 370 | * @return the head of this queue, or throws a TimeoutException if the 371 | * specified waiting time elapses before an element is available 372 | * @throws InterruptedException if interrupted while waiting 373 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 374 | */ 375 | public byte poll(long timeout, TimeUnit unit) 376 | throws InterruptedException, TimeoutException { 377 | 378 | final int readLocation = this.readLocation; 379 | int nextReadLocation = blockForReadSpace(timeout, unit, readLocation); 380 | 381 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 382 | final byte value = data[readLocation]; 383 | setReadLocation(nextReadLocation); 384 | 385 | return value; 386 | 387 | } 388 | 389 | /** 390 | * Returns true if this queue contains the specified element. 391 | * More formally, returns true if and only if this queue contains 392 | * at least one element e such that o.equals(e). The behavior of 393 | * this operation is undefined if modified while the operation is in progress. 394 | * 395 | * @param o object to be checked for containment in this queue 396 | * @return true if this queue contains the specified element 397 | * @throws ClassCastException if the class of the specified element 398 | * is incompatible with this queue 399 | * (optional) 400 | * @throws NullPointerException if the specified element is null 401 | * (optional) 402 | */ 403 | public boolean contains(int o) { 404 | 405 | int readLocation = this.readLocation; 406 | int writeLocation = this.writeLocation; 407 | 408 | for (; ; ) { 409 | 410 | if (readLocation == writeLocation) 411 | return false; 412 | 413 | if (o == data[readLocation]) 414 | return true; 415 | 416 | // sets the readLocation my moving it on by 1, this may cause it it wrap back to the start. 417 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 418 | 419 | } 420 | 421 | 422 | } 423 | 424 | /** 425 | * Removes all available elements from this queue and adds them 426 | * to the given array. If the target array is smaller than the number of elements then the number of elements read will equal the size of the array. 427 | * This operation may be more 428 | * efficient than repeatedly polling this queue. A failure 429 | * encountered while attempting to add elements to 430 | * collection c may result in elements being in neither, 431 | * either or both collections when the associated exception is 432 | * thrown. Further, the behavior of 433 | * this operation is undefined if the following methods are called during progress of this operation 434 | * {@link #take()}. {@link #offer(byte)}, {@link #put(byte)}, {@link #drainTo(byte[], int)}} 435 | * 436 | * @param target the collection to transfer elements into 437 | * @return the number of elements transferred 438 | * @throws UnsupportedOperationException if addition of elements 439 | * is not supported by the specified collection 440 | * @throws ClassCastException if the class of an element of this queue 441 | * prevents it from being added to the specified collection 442 | * @throws NullPointerException if the specified collection is null 443 | * @throws IllegalArgumentException if the specified collection is this 444 | * queue, or some property of an element of this queue prevents 445 | * it from being added to the specified collection 446 | */ 447 | int drainTo(byte[] target) { 448 | return drainTo(target, target.length); 449 | } 450 | 451 | /** 452 | * Removes at most the given number of available elements from 453 | * this queue and adds them to the given collection. A failure 454 | * encountered while attempting to add elements to 455 | * collection c may result in elements being in neither, 456 | * either or both collections when the associated exception is 457 | * thrown. Attempts to drain a queue to itself result in 458 | * IllegalArgumentException. Further, the behavior of 459 | * this operation is undefined if the specified collection is 460 | * modified while the operation is in progress. 461 | * 462 | * @param target the array to transfer elements into 463 | * @param maxElements the maximum number of elements to transfer 464 | * @return the number of elements transferred 465 | * @throws UnsupportedOperationException if addition of elements 466 | * is not supported by the specified collection 467 | * @throws ClassCastException if the class of an element of this queue 468 | * prevents it from being added to the specified collection 469 | * @throws NullPointerException if the specified collection is null 470 | * @throws IllegalArgumentException if the specified collection is this 471 | * queue, or some property of an element of this queue prevents 472 | * it from being added to the specified collection 473 | */ 474 | int drainTo(byte[] target, int maxElements) { 475 | 476 | // we want to minimize the number of volatile reads, so we read the readLocation just once. 477 | int readLocation = this.readLocation; 478 | 479 | int i = 0; 480 | 481 | // to reduce the number of volatile reads we are going to perform a kind of double check reading on the volatile write location 482 | int writeLocation = this.writeLocation; 483 | 484 | do { 485 | 486 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka size()=0) 487 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 488 | if (writeLocation == readLocation) { 489 | 490 | writeLocation = this.writeLocation; 491 | 492 | 493 | if (writeLocation == readLocation) { 494 | 495 | setReadLocation(readLocation); 496 | return i; 497 | } 498 | } 499 | 500 | 501 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 502 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 503 | 504 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 505 | target[i] = data[readLocation]; 506 | 507 | 508 | } while (i <= maxElements); 509 | 510 | setReadLocation(readLocation); 511 | 512 | return maxElements; 513 | } 514 | } 515 | 516 | 517 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/ConcurrentBlockingDoubleQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | 6 | /** 7 | * A low latency, lock free, primitive bounded blocking queue backed by an double[]. This class mimics the 8 | * interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, however works with primitive 9 | * doubles rather than {@link Object}s, so is unable to actually implement the BlockingQueue .

This class 10 | * takes advantage of the Unsafe.putOrderedInt, which allows us to create non-blocking code with guaranteed 11 | * writes. These writes will not be re-ordered by instruction reordering. Under the covers it uses the faster 12 | * store-store barrier, rather than the the slower store-load barrier, which is used when doing a volatile 13 | * write. One of the trade off with this improved performance is we are limited to a single producer, single 14 | * consumer. For further information on this see, the blog post A 15 | * Faster Volatile by Rob Austin.

This queue orders elements FIFO (first-in-first-out). The 16 | * head of the queue is that element that has been on the queue the longest time. The tail 17 | * of the queue is that element that has been on the queue the shortest time. New elements are inserted at the 18 | * tail of the queue, and the queue retrieval operations obtain elements at the head of the queue.

This 19 | * is a classic "bounded buffer", in which a fixed-capacityd array holds elements inserted by 20 | * producers and extracted by consumers. Once created, the capacity cannot be changed. Attempts to {@link 21 | * #put put(e)} an element into a full queue will result in the operation blocking; attempts to {@link #take 22 | * take()} an element from an empty queue will similarly block.

Due to the lock free nature of its 23 | * implementation, ordering works on a first come first served basis.

Methods come in four forms, with 24 | * different ways of handling operations that cannot be satisfied immediately, but may be satisfied at some 25 | * point in the future: one throws an exception, the second returns a special value (either null or 26 | * false, depending on the operation), the third blocks the current thread indefinitely until the 27 | * operation can succeed, and the fourth blocks for only a given maximum time limit before giving up. These 28 | * methods are summarized in the following table: 29 | * 31 | * 33 | * 35 | * > 37 | *
Throws exception Special value Blocks Times out
Insert {@link #add add(e)} {@link #offer offer(e)} {@link #put 32 | * put(e)} {@link #offer(double, long, java.util.concurrent.TimeUnit) offer(e, time, unit)}
Remove {@link #poll poll()} not applicable {@link #take 34 | * long()} {@link #poll(long, java.util.concurrent.TimeUnit) poll(time, unit)}
Examine not applicable not applicable not 36 | * applicable {@link #peek(long, java.util.concurrent.TimeUnit) peek(time, unit)}

A uk.co.boundedbuffer.ConcurrentBlockingDoubleQueue is capacity bounded. At 38 | * any given time it may have a remainingCapacity beyond which no additional elements can be 39 | * put without blocking.

It is not possible to remove an arbitrary element from a queue using 40 | * remove(x). As this operation would not performed very efficiently.

All of 41 | * uk.co.boundedbuffer.ConcurrentBlockingDoubleQueue methods are thread-safe when used with a single 42 | * producer and single consumer, internal atomicity is achieved using lock free strategies, such as sping 43 | * locks.

Like a BlockingQueue, the uk.co.boundedbuffer.ConcurrentBlockingDoubleQueue does 44 | * not intrinsically support any kind of "close" or "shutdown" operation to 45 | * indicate that no more items will be added. The needs and usage of such features tend to be 46 | * implementation-dependent. For example, a common tactic is for producers to insert special 47 | * end-of-stream or poison objects, that are interpreted accordingly when taken by 48 | * consumers.

Usage example, based on a typical producer-consumer scenario. Note that a 49 | * BlockingQueue can ONLY be used with a single producers and single consumer thead. 50 | *

 51 |  *
 52 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 53 |  *
 54 |  *  public void run() {
 55 |  *       queue.put(1);
 56 |  *       }
 57 |  *  });
 58 |  *
 59 |  *
 60 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 61 |  *
 62 |  *  public void run() {
 63 |  *           final int value = queue.take();
 64 |  *       }
 65 |  *  });
 66 |  * 
67 | *

Memory consistency effects: As with other concurrent collections, actions in a thread prior to 68 | * placing an object into a {@code BlockingQueue} happen-before 69 | * actions subsequent to the access or removal of that element from the {@code BlockingQueue} in another 70 | * thread.

Copyright 2014 Rob Austin

Licensed under the Apache License, Version 2.0 (the 71 | * "License"); you may not use this file except in compliance with the License. You may obtain a copy of the 72 | * License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed 73 | * to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 74 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 75 | * governing permissions and limitations under the License. 76 | * 77 | * @author Rob Austin 78 | * @since 1.1 79 | */ 80 | public class ConcurrentBlockingDoubleQueue extends AbstractBlockingQueue { 81 | 82 | // intentionally not volatile, as we are carefully ensuring that the memory barriers are controlled below by other objects 83 | private final double[] data = new double[capacity]; 84 | 85 | 86 | /** 87 | * Creates an BlockingQueue with the default capacity of 1024 88 | */ 89 | public ConcurrentBlockingDoubleQueue() { 90 | 91 | } 92 | 93 | /** 94 | * @param capacity Creates an BlockingQueue with the given (fixed) capacity 95 | */ 96 | public ConcurrentBlockingDoubleQueue(int capacity) { 97 | super(capacity); 98 | } 99 | 100 | 101 | /** 102 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 103 | * capacity restrictions, returning true upon success and throwing an 104 | * IllegalStateException if no space is currently available. When using a capacity-restricted 105 | * queue, it is generally preferable to use {@link #offer(double) offer}. 106 | * 107 | * @param value the element to add 108 | * @return true (as specified by {@link java.util.Collection#add}) 109 | * @throws IllegalStateException if the element cannot be added at this time due to capacity 110 | * restrictions 111 | * @throws ClassCastException if the class of the specified element prevents it from being added to 112 | * this queue 113 | * @throws NullPointerException if the specified element is null 114 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 115 | * to this queue 116 | */ 117 | public boolean add(double value) { 118 | 119 | // volatile read 120 | final int writeLocation = this.producerWriteLocation; 121 | 122 | final int nextWriteLocation = getNextWriteLocationThrowIfFull(writeLocation); 123 | 124 | // purposely not volatile 125 | data[writeLocation] = value; 126 | 127 | setWriteLocation(nextWriteLocation); 128 | return true; 129 | } 130 | 131 | /** 132 | * Retrieves and removes the head of this queue, waiting if necessary until an element becomes available. 133 | *

the reads must always occur on the same thread 134 | * 135 | * @return the head of this queue 136 | * @throws InterruptedException if interrupted while waiting 137 | */ 138 | public double take() { 139 | 140 | // non volatile read ( which is quicker ) 141 | final int readLocation = this.consumerReadLocation; 142 | 143 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 144 | final int nextReadLocation = blockForReadSpace(readLocation); 145 | 146 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 147 | final double value = data[readLocation]; 148 | 149 | setReadLocation(nextReadLocation); 150 | 151 | return value; 152 | 153 | } 154 | 155 | /** 156 | * Retrieves, but does not remove, the head of this queue. 157 | * 158 | * @param timeout how long to wait before giving up, in units of unit 159 | * @param unit a TimeUnit determining how to interpret the timeout parameter 160 | * @return the head of this queue 161 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 162 | */ 163 | 164 | public double peek(long timeout, TimeUnit unit) 165 | throws InterruptedException, TimeoutException { 166 | 167 | // non volatile read ( which is quicker ) 168 | final int readLocation = this.consumerReadLocation; 169 | 170 | blockForReadSpace(timeout, unit, readLocation); 171 | 172 | // purposely not volatile as the read memory barrier occurred above when we read ' this.readLocation' 173 | return data[readLocation]; 174 | 175 | } 176 | 177 | /** 178 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 179 | * capacity restrictions, returning true upon success and false if no space is currently 180 | * available. When using a capacity-restricted queue, this method is generally preferable to {@link 181 | * #add}, which can fail to insert an element only by throwing an exception. 182 | * 183 | * @param value the element to add 184 | * @return true if the element was added to this queue, else false 185 | * @throws NullPointerException if the specified element is null 186 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 187 | * to this queue 188 | */ 189 | public boolean offer(double value) { 190 | 191 | // non volatile read ( which is quicker ) 192 | final int writeLocation = this.producerWriteLocation; 193 | 194 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 195 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 196 | 197 | if (nextWriteLocation == capacity - 1) { 198 | 199 | if (readLocation == 0) 200 | return false; 201 | 202 | } else if (nextWriteLocation + 1 == readLocation) 203 | return false; 204 | 205 | // purposely not volatile see the comment below 206 | data[writeLocation] = value; 207 | 208 | setWriteLocation(nextWriteLocation); 209 | return true; 210 | } 211 | 212 | /** 213 | * Inserts the specified element into this queue, waiting if necessary for space to become available. 214 | * 215 | * @param value the element to add 216 | * @throws InterruptedException if interrupted while waiting 217 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 218 | * to this queue 219 | */ 220 | public void put(double value) throws InterruptedException { 221 | 222 | final int writeLocation1 = this.producerWriteLocation; 223 | final int nextWriteLocation = blockForWriteSpace(writeLocation1); 224 | 225 | // purposely not volatile see the comment below 226 | data[writeLocation1] = value; 227 | 228 | setWriteLocation(nextWriteLocation); 229 | } 230 | 231 | /** 232 | * Inserts the specified element into this queue, waiting up to the specified wait time if necessary for 233 | * space to become available. 234 | * 235 | * @param value the element to add 236 | * @param timeout how long to wait before giving up, in units of unit 237 | * @param unit a TimeUnit determining how to interpret the timeout parameter 238 | * @return true if successful, or false if the specified waiting time elapses before 239 | * space is available 240 | * @throws InterruptedException if interrupted while waiting 241 | */ 242 | public boolean offer(double value, long timeout, TimeUnit unit) 243 | throws InterruptedException { 244 | 245 | // non volatile read ( which is quicker ) 246 | final int writeLocation = this.producerWriteLocation; 247 | 248 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 249 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 250 | 251 | 252 | if (nextWriteLocation == capacity - 1) { 253 | 254 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 255 | 256 | while (readLocation == 0) 257 | // this condition handles the case where writer has caught up with the read, 258 | // we will wait for a read, ( which will cause a change on the read location ) 259 | { 260 | if (!blockAtAdd(timeoutAt)) 261 | return false; 262 | } 263 | 264 | } else { 265 | 266 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 267 | 268 | while (nextWriteLocation == readLocation) 269 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 270 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 271 | { 272 | if (!blockAtAdd(timeoutAt)) 273 | return false; 274 | } 275 | } 276 | 277 | // purposely not volatile see the comment below 278 | data[writeLocation] = value; 279 | 280 | // the line below, is where the write memory barrier occurs, 281 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 282 | setWriteLocation(nextWriteLocation); 283 | 284 | return true; 285 | } 286 | 287 | /** 288 | * Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an 289 | * element to become available. 290 | * 291 | * @param timeout how long to wait before giving up, in units of unit 292 | * @param unit a TimeUnit determining how to interpret the timeout parameter 293 | * @return the head of this queue, or throws a TimeoutException if the specified waiting time 294 | * elapses before an element is available 295 | * @throws InterruptedException if interrupted while waiting 296 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 297 | */ 298 | public double poll(long timeout, TimeUnit unit) 299 | throws InterruptedException, TimeoutException { 300 | 301 | final int readLocation = this.consumerReadLocation; 302 | int nextReadLocation = blockForReadSpace(timeout, unit, readLocation); 303 | 304 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 305 | final double value = data[readLocation]; 306 | setReadLocation(nextReadLocation); 307 | 308 | return value; 309 | 310 | } 311 | 312 | /** 313 | * Returns true if this queue contains the specified element. More formally, returns 314 | * true if and only if this queue contains at least one element e such that 315 | * o.equals(e). The behavior of this operation is undefined if modified while the operation is in 316 | * progress. 317 | * 318 | * @param o object to be checked for containment in this queue 319 | * @return true if this queue contains the specified element 320 | * @throws ClassCastException if the class of the specified element is incompatible with this queue (optional) 322 | * @throws NullPointerException if the specified element is null (optional) 323 | */ 324 | public boolean contains(int o) { 325 | 326 | int readLocation = this.readLocation; 327 | int writeLocation = this.writeLocation; 328 | 329 | for (; ; ) { 330 | 331 | if (readLocation == writeLocation) 332 | return false; 333 | 334 | if (o == data[readLocation]) 335 | return true; 336 | 337 | // sets the readLocation my moving it on by 1, this may cause it it wrap back to the start. 338 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 339 | 340 | } 341 | 342 | 343 | } 344 | 345 | /** 346 | * Removes all available elements from this queue and adds them to the given array. If the target array is 347 | * smaller than the number of elements then the number of elements read will equal the capacity of the 348 | * array. This operation may be more efficient than repeatedly polling this queue. A failure encountered 349 | * while attempting to add elements to collection c may result in elements being in neither, 350 | * either or both collections when the associated exception is thrown. Further, the behavior of this 351 | * operation is undefined if the following methods are called during progress of this operation {@link 352 | * #take()}. {@link #offer(double)}, {@link #put(double)}, {@link #drainTo(double[], int)}} 353 | * 354 | * @param target the collection to transfer elements into 355 | * @return the number of elements transferred 356 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 357 | * collection 358 | * @throws ClassCastException if the class of an element of this queue prevents it from being 359 | * added to the specified collection 360 | * @throws NullPointerException if the specified collection is null 361 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 362 | * element of this queue prevents it from being added to the 363 | * specified collection 364 | */ 365 | int drainTo(double[] target) { 366 | return drainTo(target, target.length); 367 | } 368 | 369 | /** 370 | * Removes at most the given number of available elements from this queue and adds them to the given 371 | * collection. A failure encountered while attempting to add elements to collection c may result 372 | * in elements being in neither, either or both collections when the associated exception is thrown. 373 | * Attempts to drain a queue to itself result in IllegalArgumentException. Further, the behavior 374 | * of this operation is undefined if the specified collection is modified while the operation is in 375 | * progress. 376 | * 377 | * @param target the array to transfer elements into 378 | * @param maxElements the maximum number of elements to transfer 379 | * @return the number of elements transferred 380 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 381 | * collection 382 | * @throws ClassCastException if the class of an element of this queue prevents it from being 383 | * added to the specified collection 384 | * @throws NullPointerException if the specified collection is null 385 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 386 | * element of this queue prevents it from being added to the 387 | * specified collection 388 | */ 389 | int drainTo(double[] target, int maxElements) { 390 | 391 | // non volatile read ( which is quicker ) 392 | int readLocation = this.consumerReadLocation; 393 | 394 | int i = 0; 395 | 396 | // to reduce the number of volatile reads we are going to perform a kind of double check reading on the volatile write location 397 | int writeLocation = this.writeLocation; 398 | 399 | do { 400 | 401 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka capacity()=0) 402 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 403 | if (writeLocation == readLocation) { 404 | 405 | writeLocation = this.writeLocation; 406 | 407 | 408 | if (writeLocation == readLocation) { 409 | 410 | setReadLocation(readLocation); 411 | return i; 412 | } 413 | } 414 | 415 | 416 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 417 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 418 | 419 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 420 | target[i] = data[readLocation]; 421 | 422 | 423 | } while (i <= maxElements); 424 | 425 | setReadLocation(readLocation); 426 | 427 | return maxElements; 428 | } 429 | } 430 | 431 | 432 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/ConcurrentBlockingFloatQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | 6 | /** 7 | * A low latency, lock free, primitive bounded blocking queue backed by an float[]. This class mimics the 8 | * interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, however works with primitive 9 | * floats rather than {@link Object}s, so is unable to actually implement the BlockingQueue .

This class 10 | * takes advantage of the Unsafe.putOrderedInt, which allows us to create non-blocking code with guaranteed 11 | * writes. These writes will not be re-ordered by instruction reordering. Under the covers it uses the faster 12 | * store-store barrier, rather than the the slower store-load barrier, which is used when doing a volatile 13 | * write. One of the trade off with this improved performance is we are limited to a single producer, single 14 | * consumer. For further information on this see, the blog post A 15 | * Faster Volatile by Rob Austin.

This queue orders elements FIFO (first-in-first-out). The 16 | * head of the queue is that element that has been on the queue the longest time. The tail 17 | * of the queue is that element that has been on the queue the shortest time. New elements are inserted at the 18 | * tail of the queue, and the queue retrieval operations obtain elements at the head of the queue.

This 19 | * is a classic "bounded buffer", in which a fixed-sized array holds elements inserted by producers 20 | * and extracted by consumers. Once created, the capacity cannot be changed. Attempts to {@link #put put(e)} 21 | * an element into a full queue will result in the operation blocking; attempts to {@link #take take()} an 22 | * element from an empty queue will similarly block.

Due to the lock free nature of its 23 | * implementation, ordering works on a first come first served basis.

Methods come in four forms, with 24 | * different ways of handling operations that cannot be satisfied immediately, but may be satisfied at some 25 | * point in the future: one throws an exception, the second returns a special value (either null or 26 | * false, depending on the operation), the third blocks the current thread indefinitely until the 27 | * operation can succeed, and the fourth blocks for only a given maximum time limit before giving up. These 28 | * methods are summarized in the following table: 29 | * 31 | * 33 | * 35 | * > 37 | *
Throws exception Special value Blocks Times out
Insert {@link #add add(e)} {@link #offer offer(e)} {@link #put 32 | * put(e)} {@link #offer(float, long, java.util.concurrent.TimeUnit) offer(e, time, unit)}
Remove {@link #poll poll()} not applicable {@link #take 34 | * long()} {@link #poll(long, java.util.concurrent.TimeUnit) poll(time, unit)}
Examine not applicable not applicable not 36 | * applicable {@link #peek(long, java.util.concurrent.TimeUnit) peek(time, unit)}

A uk.co.boundedbuffer.ConcurrentBlockingFloatQueue is capacity bounded. At any 38 | * given time it may have a remainingCapacity beyond which no additional elements can be put 39 | * without blocking.

It is not possible to remove an arbitrary element from a queue using 40 | * remove(x). As this operation would not performed very efficiently.

All of 41 | * uk.co.boundedbuffer.ConcurrentBlockingFloatQueue methods are thread-safe when used with a single 42 | * producer and single consumer, internal atomicity is achieved using lock free strategies, such as sping 43 | * locks.

Like a BlockingQueue, the uk.co.boundedbuffer.ConcurrentBlockingFloatQueue does 44 | * not intrinsically support any kind of "close" or "shutdown" operation to 45 | * indicate that no more items will be added. The needs and usage of such features tend to be 46 | * implementation-dependent. For example, a common tactic is for producers to insert special 47 | * end-of-stream or poison objects, that are interpreted accordingly when taken by 48 | * consumers.

Usage example, based on a typical producer-consumer scenario. Note that a 49 | * BlockingQueue can ONLY be used with a single producers and single consumer thead. 50 | *

 51 |  *
 52 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 53 |  *
 54 |  *  public void run() {
 55 |  *       queue.put(1);
 56 |  *       }
 57 |  *  });
 58 |  *
 59 |  *
 60 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 61 |  *
 62 |  *  public void run() {
 63 |  *           final int value = queue.take();
 64 |  *       }
 65 |  *  });
 66 |  * 
67 | *

Memory consistency effects: As with other concurrent collections, actions in a thread prior to 68 | * placing an object into a {@code BlockingQueue} happen-before 69 | * actions subsequent to the access or removal of that element from the {@code BlockingQueue} in another 70 | * thread.

Copyright 2014 Rob Austin

Licensed under the Apache License, Version 2.0 (the 71 | * "License"); you may not use this file except in compliance with the License. You may obtain a copy of the 72 | * License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed 73 | * to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 74 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 75 | * governing permissions and limitations under the License. 76 | * 77 | * @author Rob Austin 78 | * @since 1.1 79 | */ 80 | public class ConcurrentBlockingFloatQueue extends AbstractBlockingQueue { 81 | 82 | // intentionally not volatile, as we are carefully ensuring that the memory barriers are controlled below by other objects 83 | private final float[] data = new float[capacity]; 84 | 85 | 86 | /** 87 | * Creates an BlockingQueue with the default capacity of 1024 88 | */ 89 | public ConcurrentBlockingFloatQueue() { 90 | 91 | } 92 | 93 | /** 94 | * @param size Creates an BlockingQueue with the given (fixed) capacity 95 | */ 96 | public ConcurrentBlockingFloatQueue(int size) { 97 | super(size); 98 | } 99 | 100 | /** 101 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 102 | * capacity restrictions, returning true upon success and throwing an 103 | * IllegalStateException if no space is currently available. When using a capacity-restricted 104 | * queue, it is generally preferable to use {@link #offer(float) offer}. 105 | * 106 | * @param value the element to add 107 | * @return true (as specified by {@link java.util.Collection#add}) 108 | * @throws IllegalStateException if the element cannot be added at this time due to capacity 109 | * restrictions 110 | * @throws ClassCastException if the class of the specified element prevents it from being added to 111 | * this queue 112 | * @throws NullPointerException if the specified element is null 113 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 114 | * to this queue 115 | */ 116 | public boolean add(float value) { 117 | 118 | // volatile read 119 | final int writeLocation = this.producerWriteLocation; 120 | 121 | final int nextWriteLocation = getNextWriteLocationThrowIfFull(writeLocation); 122 | 123 | // purposely not volatile 124 | data[writeLocation] = value; 125 | 126 | setWriteLocation(nextWriteLocation); 127 | return true; 128 | } 129 | 130 | /** 131 | * Retrieves and removes the head of this queue, waiting if necessary until an element becomes available. 132 | *

the reads must always occur on the same thread 133 | * 134 | * @return the head of this queue 135 | */ 136 | public float take() { 137 | 138 | // non volatile read ( which is quicker ) 139 | final int readLocation = this.consumerReadLocation; 140 | 141 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 142 | final int nextReadLocation = blockForReadSpace(readLocation); 143 | 144 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 145 | final float value = data[readLocation]; 146 | 147 | setReadLocation(nextReadLocation); 148 | 149 | return value; 150 | 151 | } 152 | 153 | /** 154 | * Retrieves, but does not remove, the head of this queue. 155 | * 156 | * @param timeout how long to wait before giving up, in units of unit 157 | * @param unit a TimeUnit determining how to interpret the timeout parameter 158 | * @return the head of this queue 159 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 160 | */ 161 | 162 | public float peek(long timeout, TimeUnit unit) 163 | throws InterruptedException, TimeoutException { 164 | 165 | // non volatile read ( which is quicker ) 166 | final int readLocation = this.consumerReadLocation; 167 | 168 | blockForReadSpace(timeout, unit, readLocation); 169 | 170 | // purposely not volatile as the read memory barrier occurred above when we read ' this.readLocation' 171 | return data[readLocation]; 172 | 173 | } 174 | 175 | /** 176 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 177 | * capacity restrictions, returning true upon success and false if no space is currently 178 | * available. When using a capacity-restricted queue, this method is generally preferable to {@link 179 | * #add}, which can fail to insert an element only by throwing an exception. 180 | * 181 | * @param value the element to add 182 | * @return true if the element was added to this queue, else false 183 | * @throws NullPointerException if the specified element is null 184 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 185 | * to this queue 186 | */ 187 | public boolean offer(float value) { 188 | 189 | // non volatile read ( which is quicker ) 190 | final int writeLocation = this.producerWriteLocation; 191 | 192 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 193 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 194 | 195 | if (nextWriteLocation == capacity - 1) { 196 | 197 | if (readLocation == 0) 198 | return false; 199 | 200 | } else if (nextWriteLocation + 1 == readLocation) 201 | return false; 202 | 203 | // purposely not volatile see the comment below 204 | data[writeLocation] = value; 205 | 206 | setWriteLocation(nextWriteLocation); 207 | return true; 208 | } 209 | 210 | /** 211 | * Inserts the specified element into this queue, waiting if necessary for space to become available. 212 | * 213 | * @param value the element to add 214 | * @throws InterruptedException if interrupted while waiting 215 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 216 | * to this queue 217 | */ 218 | public void put(float value) throws InterruptedException { 219 | 220 | final int writeLocation1 = this.producerWriteLocation; 221 | final int nextWriteLocation = blockForWriteSpace(writeLocation1); 222 | 223 | // purposely not volatile see the comment below 224 | data[writeLocation1] = value; 225 | 226 | setWriteLocation(nextWriteLocation); 227 | } 228 | 229 | /** 230 | * Inserts the specified element into this queue, waiting up to the specified wait time if necessary for 231 | * space to become available. 232 | * 233 | * @param value the element to add 234 | * @param timeout how long to wait before giving up, in units of unit 235 | * @param unit a TimeUnit determining how to interpret the timeout parameter 236 | * @return true if successful, or false if the specified waiting time elapses before 237 | * space is available 238 | * @throws InterruptedException if interrupted while waiting 239 | */ 240 | public boolean offer(float value, long timeout, TimeUnit unit) 241 | throws InterruptedException { 242 | 243 | // non volatile read ( which is quicker ) 244 | final int writeLocation = this.producerWriteLocation; 245 | 246 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 247 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 248 | 249 | 250 | if (nextWriteLocation == capacity - 1) { 251 | 252 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 253 | 254 | while (readLocation == 0) 255 | // this condition handles the case where writer has caught up with the read, 256 | // we will wait for a read, ( which will cause a change on the read location ) 257 | { 258 | if (!blockAtAdd(timeoutAt)) 259 | return false; 260 | } 261 | 262 | } else { 263 | 264 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 265 | 266 | while (nextWriteLocation == readLocation) 267 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 268 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 269 | { 270 | if (!blockAtAdd(timeoutAt)) 271 | return false; 272 | } 273 | } 274 | 275 | // purposely not volatile see the comment below 276 | data[writeLocation] = value; 277 | 278 | // the line below, is where the write memory barrier occurs, 279 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 280 | setWriteLocation(nextWriteLocation); 281 | 282 | return true; 283 | } 284 | 285 | /** 286 | * Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an 287 | * element to become available. 288 | * 289 | * @param timeout how long to wait before giving up, in units of unit 290 | * @param unit a TimeUnit determining how to interpret the timeout parameter 291 | * @return the head of this queue, or throws a TimeoutException if the specified waiting time 292 | * elapses before an element is available 293 | * @throws InterruptedException if interrupted while waiting 294 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 295 | */ 296 | public float poll(long timeout, TimeUnit unit) 297 | throws InterruptedException, TimeoutException { 298 | 299 | final int readLocation = this.consumerReadLocation; 300 | int nextReadLocation = blockForReadSpace(timeout, unit, readLocation); 301 | 302 | // purposely non volatile as the read memory barrier occurred when we read 'writeLocation' 303 | final float value = data[readLocation]; 304 | setReadLocation(nextReadLocation); 305 | 306 | return value; 307 | 308 | } 309 | 310 | /** 311 | * Returns true if this queue contains the specified element. More formally, returns 312 | * true if and only if this queue contains at least one element e such that 313 | * o.equals(e). The behavior of this operation is undefined if modified while the operation is in 314 | * progress. 315 | * 316 | * @param o object to be checked for containment in this queue 317 | * @return true if this queue contains the specified element 318 | * @throws ClassCastException if the class of the specified element is incompatible with this queue (optional) 320 | * @throws NullPointerException if the specified element is null (optional) 321 | */ 322 | public boolean contains(int o) { 323 | 324 | int readLocation = this.readLocation; 325 | int writeLocation = this.writeLocation; 326 | 327 | for (; ; ) { 328 | 329 | if (readLocation == writeLocation) 330 | return false; 331 | 332 | if (o == data[readLocation]) 333 | return true; 334 | 335 | // sets the readLocation my moving it on by 1, this may cause it it wrap back to the start. 336 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 337 | 338 | } 339 | 340 | 341 | } 342 | 343 | /** 344 | * Removes all available elements from this queue and adds them to the given array. If the target array is 345 | * smaller than the number of elements then the number of elements read will equal the size of the array. 346 | * This operation may be more efficient than repeatedly polling this queue. A failure encountered while 347 | * attempting to add elements to collection c may result in elements being in neither, either or 348 | * both collections when the associated exception is thrown. Further, the behavior of this operation is 349 | * undefined if the following methods are called during progress of this operation {@link #take()}. {@link 350 | * #offer(float)}, {@link #put(float)}, {@link #drainTo(float[], int)}} 351 | * 352 | * @param target the collection to transfer elements into 353 | * @return the number of elements transferred 354 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 355 | * collection 356 | * @throws ClassCastException if the class of an element of this queue prevents it from being 357 | * added to the specified collection 358 | * @throws NullPointerException if the specified collection is null 359 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 360 | * element of this queue prevents it from being added to the 361 | * specified collection 362 | */ 363 | int drainTo(float[] target) { 364 | return drainTo(target, target.length); 365 | } 366 | 367 | /** 368 | * Removes at most the given number of available elements from this queue and adds them to the given 369 | * collection. A failure encountered while attempting to add elements to collection c may result 370 | * in elements being in neither, either or both collections when the associated exception is thrown. 371 | * Attempts to drain a queue to itself result in IllegalArgumentException. Further, the behavior 372 | * of this operation is undefined if the specified collection is modified while the operation is in 373 | * progress. 374 | * 375 | * @param target the array to transfer elements into 376 | * @param maxElements the maximum number of elements to transfer 377 | * @return the number of elements transferred 378 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 379 | * collection 380 | * @throws ClassCastException if the class of an element of this queue prevents it from being 381 | * added to the specified collection 382 | * @throws NullPointerException if the specified collection is null 383 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 384 | * element of this queue prevents it from being added to the 385 | * specified collection 386 | */ 387 | int drainTo(float[] target, int maxElements) { 388 | 389 | // non volatile read ( which is quicker ) 390 | int readLocation = this.consumerReadLocation; 391 | 392 | int i = 0; 393 | 394 | // to reduce the number of volatile reads we are going to perform a kind of double check reading on the volatile write location 395 | int writeLocation = this.writeLocation; 396 | 397 | do { 398 | 399 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka size()=0) 400 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 401 | if (writeLocation == readLocation) { 402 | 403 | writeLocation = this.writeLocation; 404 | 405 | 406 | if (writeLocation == readLocation) { 407 | 408 | setReadLocation(readLocation); 409 | return i; 410 | } 411 | } 412 | 413 | 414 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 415 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 416 | 417 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 418 | target[i] = data[readLocation]; 419 | 420 | 421 | } while (i <= maxElements); 422 | 423 | setReadLocation(readLocation); 424 | 425 | return maxElements; 426 | } 427 | 428 | } 429 | 430 | 431 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/ConcurrentBlockingIntQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | 6 | /** 7 | * A low latency, lock free, primitive bounded blocking queue backed by an int[]. This class mimics the 8 | * interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, however works with primitive 9 | * ints rather than {@link Object}s, so is unable to actually implement the BlockingQueue .

This class 10 | * takes advantage of the Unsafe.putOrderedInt, which allows us to create non-blocking code with guaranteed 11 | * writes. These writes will not be re-ordered by instruction reordering. Under the covers it uses the faster 12 | * store-store barrier, rather than the the slower store-load barrier, which is used when doing a volatile 13 | * write. One of the trade off with this improved performance is we are limited to a single producer, single 14 | * consumer. For further information on this see, the blog post A 15 | * Faster Volatile by Rob Austin.

This queue orders elements FIFO (first-in-first-out). The 16 | * head of the queue is that element that has been on the queue the longest time. The tail 17 | * of the queue is that element that has been on the queue the shortest time. New elements are inserted at the 18 | * tail of the queue, and the queue retrieval operations obtain elements at the head of the queue.

This 19 | * is a classic "bounded buffer", in which a fixed-sized array holds elements inserted by producers 20 | * and extracted by consumers. Once created, the capacity cannot be changed. Attempts to {@link #put put(e)} 21 | * an element into a full queue will result in the operation blocking; attempts to {@link #take take()} an 22 | * element from an empty queue will similarly block.

Due to the lock free nature of its 23 | * implementation, ordering works on a first come first served basis.

Methods come in four forms, with 24 | * different ways of handling operations that cannot be satisfied immediately, but may be satisfied at some 25 | * point in the future: one throws an exception, the second returns a special value (either null or 26 | * false, depending on the operation), the third blocks the current thread indefinitely until the 27 | * operation can succeed, and the fourth blocks for only a given maximum time limit before giving up. These 28 | * methods are summarized in the following table: 29 | * 31 | * 33 | * 35 | * 36 | * >
Throws exception Special value Blocks Times out
Insert {@link #add add(e)} {@link #offer offer(e)} {@link #put 32 | * put(e)} {@link #offer(int, long, java.util.concurrent.TimeUnit) offer(e, time, unit)}
Remove {@link #poll poll()} not applicable {@link #take 34 | * take()} {@link #poll(long, TimeUnit) poll(time, unit)}
Examinenot applicable not applicable not applicable{@link #peek(long, TimeUnit) peek(time, unit)}

A 37 | * uk.co.boundedbuffer.ConcurrentBlockingIntQueue is capacity bounded. At any given time it may have 38 | * a remainingCapacity beyond which no additional elements can be put without blocking.

39 | *

It is not possible to remove an arbitrary element from a queue using remove(x). As this 40 | * operation would not performed very efficiently.

All of uk.co.boundedbuffer.ConcurrentBlockingIntQueue 41 | * methods are thread-safe when used with a single producer and single consumer, internal atomicity is 42 | * achieved using lock free strategies, such as sping locks.

Like a BlockingQueue, the 43 | * uk.co.boundedbuffer.ConcurrentBlockingIntQueue does not intrinsically support any kind of 44 | * "close" or "shutdown" operation to indicate that no more items will be added. The 45 | * needs and usage of such features tend to be implementation-dependent. For example, a common tactic is for 46 | * producers to insert special end-of-stream or poison objects, that are interpreted 47 | * accordingly when taken by consumers.

Usage example, based on a typical producer-consumer scenario. 48 | * Note that a BlockingQueue can ONLY be used with a single producers and single consumer thead. 49 | *

 50 |  *
 51 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 52 |  *
 53 |  *  public void run() {
 54 |  *       queue.put(1);
 55 |  *       }
 56 |  *  });
 57 |  *
 58 |  *
 59 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 60 |  *
 61 |  *  public void run() {
 62 |  *           final int value = queue.take();
 63 |  *       }
 64 |  *  });
 65 |  * 
66 | *

Memory consistency effects: As with other concurrent collections, actions in a thread prior to 67 | * placing an object into a {@code BlockingQueue} happen-before 68 | * actions subsequent to the access or removal of that element from the {@code BlockingQueue} in another 69 | * thread.

Copyright 2014 Rob Austin

Licensed under the Apache License, Version 2.0 (the 70 | * "License"); you may not use this file except in compliance with the License. You may obtain a copy of the 71 | * License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed 72 | * to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 73 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 74 | * governing permissions and limitations under the License. 75 | * 76 | * @author Rob Austin 77 | * @since 1.1 78 | */ 79 | public class ConcurrentBlockingIntQueue extends AbstractBlockingQueue { 80 | 81 | // intentionally not volatile, as we are carefully ensuring that the memory barriers are controlled below by other objects 82 | private final int[] data = new int[capacity]; 83 | 84 | 85 | /** 86 | * Creates an BlockingQueue with the default capacity of 1024 87 | */ 88 | public ConcurrentBlockingIntQueue() { 89 | 90 | } 91 | 92 | /** 93 | * @param size Creates an BlockingQueue with the given (fixed) capacity 94 | */ 95 | public ConcurrentBlockingIntQueue(int size) { 96 | super(size); 97 | } 98 | 99 | /** 100 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 101 | * capacity restrictions, returning true upon success and throwing an 102 | * IllegalStateException if no space is currently available. When using a capacity-restricted 103 | * queue, it is generally preferable to use {@link #offer(int) offer}. 104 | * 105 | * @param value the element to add 106 | * @return true (as specified by {@link java.util.Collection#add}) 107 | * @throws IllegalStateException if the element cannot be added at this time due to capacity 108 | * restrictions 109 | * @throws ClassCastException if the class of the specified element prevents it from being added to 110 | * this queue 111 | * @throws NullPointerException if the specified element is null 112 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 113 | * to this queue 114 | */ 115 | public boolean add(int value) { 116 | 117 | // volatile read 118 | final int writeLocation = this.producerWriteLocation; 119 | 120 | final int nextWriteLocation = getNextWriteLocationThrowIfFull(writeLocation); 121 | 122 | // purposely not volatile 123 | data[writeLocation] = value; 124 | 125 | setWriteLocation(nextWriteLocation); 126 | return true; 127 | } 128 | 129 | /** 130 | * Retrieves and removes the head of this queue, waiting if necessary until an element becomes available. 131 | *

the reads must always occur on the same thread 132 | * 133 | * @return the head of this queue 134 | */ 135 | public int take() { 136 | 137 | // non volatile read ( which is quicker ) 138 | final int readLocation = this.consumerReadLocation; 139 | 140 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 141 | final int nextReadLocation = blockForReadSpace(readLocation); 142 | 143 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 144 | final int value = data[readLocation]; 145 | 146 | setReadLocation(nextReadLocation); 147 | 148 | return value; 149 | 150 | } 151 | 152 | /** 153 | * Retrieves, but does not remove, the head of this queue. 154 | * 155 | * @param timeout how long to wait before giving up, in units of unit 156 | * @param unit a TimeUnit determining how to interpret the timeout parameter 157 | * @return the head of this queue 158 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 159 | */ 160 | 161 | public int peek(long timeout, TimeUnit unit) 162 | throws InterruptedException, TimeoutException { 163 | 164 | // non volatile read ( which is quicker ) 165 | final int readLocation = this.consumerReadLocation; 166 | 167 | blockForReadSpace(timeout, unit, readLocation); 168 | 169 | // purposely not volatile as the read memory barrier occurred above when we read ' this.readLocation' 170 | return data[readLocation]; 171 | 172 | } 173 | 174 | /** 175 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 176 | * capacity restrictions, returning true upon success and false if no space is currently 177 | * available. When using a capacity-restricted queue, this method is generally preferable to {@link 178 | * #add}, which can fail to insert an element only by throwing an exception. 179 | * 180 | * @param value the element to add 181 | * @return true if the element was added to this queue, else false 182 | * @throws NullPointerException if the specified element is null 183 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 184 | * to this queue 185 | */ 186 | public boolean offer(int value) { 187 | 188 | // non volatile read ( which is quicker ) 189 | final int writeLocation = this.producerWriteLocation; 190 | 191 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 192 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 193 | 194 | if (nextWriteLocation == capacity - 1) { 195 | 196 | if (readLocation == 0) 197 | return false; 198 | 199 | } else if (nextWriteLocation + 1 == readLocation) 200 | return false; 201 | 202 | // purposely not volatile see the comment below 203 | data[writeLocation] = value; 204 | 205 | setWriteLocation(nextWriteLocation); 206 | return true; 207 | } 208 | 209 | /** 210 | * Inserts the specified element into this queue, waiting if necessary for space to become available. 211 | * 212 | * @param value the element to add 213 | * @throws InterruptedException if interrupted while waiting 214 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 215 | * to this queue 216 | */ 217 | public void put(int value) throws InterruptedException { 218 | 219 | final int writeLocation1 = this.producerWriteLocation; 220 | final int nextWriteLocation = blockForWriteSpace(writeLocation1); 221 | 222 | // purposely not volatile see the comment below 223 | data[writeLocation1] = value; 224 | 225 | setWriteLocation(nextWriteLocation); 226 | } 227 | 228 | /** 229 | * Inserts the specified element into this queue, waiting up to the specified wait time if necessary for 230 | * space to become available. 231 | * 232 | * @param value the element to add 233 | * @param timeout how long to wait before giving up, in units of unit 234 | * @param unit a TimeUnit determining how to interpret the timeout parameter 235 | * @return true if successful, or false if the specified waiting time elapses before 236 | * space is available 237 | * @throws InterruptedException if interrupted while waiting 238 | */ 239 | public boolean offer(int value, long timeout, TimeUnit unit) 240 | throws InterruptedException { 241 | 242 | // non volatile read ( which is quicker ) 243 | final int writeLocation = this.producerWriteLocation; 244 | 245 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 246 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 247 | 248 | 249 | if (nextWriteLocation == capacity - 1) { 250 | 251 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 252 | 253 | while (readLocation == 0) 254 | // this condition handles the case where writer has caught up with the read, 255 | // we will wait for a read, ( which will cause a change on the read location ) 256 | { 257 | if (!blockAtAdd(timeoutAt)) 258 | return false; 259 | } 260 | 261 | } else { 262 | 263 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 264 | 265 | while (nextWriteLocation == readLocation) 266 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 267 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 268 | { 269 | if (!blockAtAdd(timeoutAt)) 270 | return false; 271 | } 272 | } 273 | 274 | // purposely not volatile see the comment below 275 | data[writeLocation] = value; 276 | 277 | // the line below, is where the write memory barrier occurs, 278 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 279 | setWriteLocation(nextWriteLocation); 280 | 281 | return true; 282 | } 283 | 284 | /** 285 | * Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an 286 | * element to become available. 287 | * 288 | * @param timeout how long to wait before giving up, in units of unit 289 | * @param unit a TimeUnit determining how to interpret the timeout parameter 290 | * @return the head of this queue, or throws a TimeoutException if the specified waiting time 291 | * elapses before an element is available 292 | * @throws InterruptedException if interrupted while waiting 293 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 294 | */ 295 | public int poll(long timeout, TimeUnit unit) 296 | throws InterruptedException, TimeoutException { 297 | 298 | final int readLocation = this.consumerReadLocation; 299 | int nextReadLocation = blockForReadSpace(timeout, unit, readLocation); 300 | 301 | // purposely non volatile as the read memory barrier occurred when we read 'writeLocation' 302 | final int value = data[readLocation]; 303 | setReadLocation(nextReadLocation); 304 | 305 | return value; 306 | 307 | } 308 | 309 | /** 310 | * Returns true if this queue contains the specified element. More formally, returns 311 | * true if and only if this queue contains at least one element e such that 312 | * o.equals(e). The behavior of this operation is undefined if modified while the operation is in 313 | * progress. 314 | * 315 | * @param o object to be checked for containment in this queue 316 | * @return true if this queue contains the specified element 317 | * @throws ClassCastException if the class of the specified element is incompatible with this queue (optional) 319 | * @throws NullPointerException if the specified element is null (optional) 320 | */ 321 | public boolean contains(int o) { 322 | 323 | int readLocation = this.readLocation; 324 | int writeLocation = this.writeLocation; 325 | 326 | for (; ; ) { 327 | 328 | if (readLocation == writeLocation) 329 | return false; 330 | 331 | if (o == data[readLocation]) 332 | return true; 333 | 334 | // sets the readLocation my moving it on by 1, this may cause it it wrap back to the start. 335 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 336 | 337 | } 338 | 339 | 340 | } 341 | 342 | /** 343 | * Removes all available elements from this queue and adds them to the given array. If the target array is 344 | * smaller than the number of elements then the number of elements read will equal the size of the array. 345 | * This operation may be more efficient than repeatedly polling this queue. A failure encountered while 346 | * attempting to add elements to collection c may result in elements being in neither, either or 347 | * both collections when the associated exception is thrown. Further, the behavior of this operation is 348 | * undefined if the following methods are called during progress of this operation {@link #take()}. {@link 349 | * #offer(int)}, {@link #put(int)}, {@link #drainTo(int[], int)}} 350 | * 351 | * @param target the collection to transfer elements into 352 | * @return the number of elements transferred 353 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 354 | * collection 355 | * @throws ClassCastException if the class of an element of this queue prevents it from being 356 | * added to the specified collection 357 | * @throws NullPointerException if the specified collection is null 358 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 359 | * element of this queue prevents it from being added to the 360 | * specified collection 361 | */ 362 | int drainTo(int[] target) { 363 | return drainTo(target, target.length); 364 | } 365 | 366 | /** 367 | * Removes at most the given number of available elements from this queue and adds them to the given 368 | * collection. A failure encountered while attempting to add elements to collection c may result 369 | * in elements being in neither, either or both collections when the associated exception is thrown. 370 | * Attempts to drain a queue to itself result in IllegalArgumentException. Further, the behavior 371 | * of this operation is undefined if the specified collection is modified while the operation is in 372 | * progress. 373 | * 374 | * @param target the array to transfer elements into 375 | * @param maxElements the maximum number of elements to transfer 376 | * @return the number of elements transferred 377 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 378 | * collection 379 | * @throws ClassCastException if the class of an element of this queue prevents it from being 380 | * added to the specified collection 381 | * @throws NullPointerException if the specified collection is null 382 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 383 | * element of this queue prevents it from being added to the 384 | * specified collection 385 | */ 386 | int drainTo(int[] target, int maxElements) { 387 | 388 | // non volatile read ( which is quicker ) 389 | int readLocation = this.consumerReadLocation; 390 | 391 | int i = 0; 392 | 393 | // to reduce the number of volatile reads we are going to perform a kind of double check reading on the volatile write location 394 | int writeLocation = this.writeLocation; 395 | 396 | do { 397 | 398 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka size()=0) 399 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 400 | if (writeLocation == readLocation) { 401 | 402 | writeLocation = this.writeLocation; 403 | 404 | 405 | if (writeLocation == readLocation) { 406 | 407 | setReadLocation(readLocation); 408 | return i; 409 | } 410 | } 411 | 412 | 413 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 414 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 415 | 416 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 417 | target[i] = data[readLocation]; 418 | 419 | 420 | } while (i <= maxElements); 421 | 422 | setReadLocation(readLocation); 423 | 424 | return maxElements; 425 | } 426 | 427 | } 428 | 429 | 430 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/ConcurrentBlockingLongQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | 6 | /** 7 | * A low latency, lock free, primitive bounded blocking queue backed by an long[]. This class mimics the 8 | * interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, however works with primitive 9 | * longs rather than {@link Object}s, so is unable to actually implement the BlockingQueue .

This class 10 | * takes advantage of the Unsafe.putOrderedInt, which allows us to create non-blocking code with guaranteed 11 | * writes. These writes will not be re-ordered by instruction reordering. Under the covers it uses the faster 12 | * store-store barrier, rather than the the slower store-load barrier, which is used when doing a volatile 13 | * write. One of the trade off with this improved performance is we are limited to a single producer, single 14 | * consumer. For further information on this see, the blog post A 15 | * Faster Volatile by Rob Austin.

This queue orders elements FIFO (first-in-first-out). The 16 | * head of the queue is that element that has been on the queue the longest time. The tail 17 | * of the queue is that element that has been on the queue the shortest time. New elements are inserted at the 18 | * tail of the queue, and the queue retrieval operations obtain elements at the head of the queue.

This 19 | * is a classic "bounded buffer", in which a fixed-capacityd array holds elements inserted by 20 | * producers and extracted by consumers. Once created, the capacity cannot be changed. Attempts to {@link 21 | * #put put(e)} an element into a full queue will result in the operation blocking; attempts to {@link #take 22 | * take()} an element from an empty queue will similarly block.

Due to the lock free nature of its 23 | * implementation, ordering works on a first come first served basis.

Methods come in four forms, with 24 | * different ways of handling operations that cannot be satisfied immediately, but may be satisfied at some 25 | * point in the future: one throws an exception, the second returns a special value (either null or 26 | * false, depending on the operation), the third blocks the current thread indefinitely until the 27 | * operation can succeed, and the fourth blocks for only a given maximum time limit before giving up. These 28 | * methods are summarized in the following table: 29 | * 31 | * 33 | * 35 | * > 37 | *
Throws exception Special value Blocks Times out
Insert {@link #add add(e)} {@link #offer offer(e)} {@link #put 32 | * put(e)} {@link #offer(long, long, java.util.concurrent.TimeUnit) offer(e, time, unit)}
Remove {@link #poll poll()} not applicable {@link #take 34 | * take()} {@link #poll(long, java.util.concurrent.TimeUnit) poll(time, unit)}
Examine not applicable not applicable not 36 | * applicable {@link #peek(long, java.util.concurrent.TimeUnit) peek(time, unit)}

A uk.co.boundedbuffer.ConcurrentBlockingLongQueue is capacity bounded. At any 38 | * given time it may have a remainingCapacity beyond which no additional elements can be put 39 | * without blocking.

It is not possible to remove an arbitrary element from a queue using 40 | * remove(x). As this operation would not performed very efficiently.

All of 41 | * uk.co.boundedbuffer.ConcurrentBlockingLongQueue methods are thread-safe when used with a single 42 | * producer and single consumer, internal atomicity is achieved using lock free strategies, such as sping 43 | * locks.

Like a BlockingQueue, the uk.co.boundedbuffer.ConcurrentBlockingLongQueue does 44 | * not intrinsically support any kind of "close" or "shutdown" operation to 45 | * indicate that no more items will be added. The needs and usage of such features tend to be 46 | * implementation-dependent. For example, a common tactic is for producers to insert special 47 | * end-of-stream or poison objects, that are interpreted accordingly when taken by 48 | * consumers.

Usage example, based on a typical producer-consumer scenario. Note that a 49 | * BlockingQueue can ONLY be used with a single producers and single consumer thead. 50 | *

 51 |  *
 52 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 53 |  *
 54 |  *  public void run() {
 55 |  *       queue.put(1);
 56 |  *       }
 57 |  *  });
 58 |  *
 59 |  *
 60 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 61 |  *
 62 |  *  public void run() {
 63 |  *           final int value = queue.take();
 64 |  *       }
 65 |  *  });
 66 |  * 
67 | *

Memory consistency effects: As with other concurrent collections, actions in a thread prior to 68 | * placing an object into a {@code BlockingQueue} happen-before 69 | * actions subsequent to the access or removal of that element from the {@code BlockingQueue} in another 70 | * thread.

Copyright 2014 Rob Austin

Licensed under the Apache License, Version 2.0 (the 71 | * "License"); you may not use this file except in compliance with the License. You may obtain a copy of the 72 | * License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed 73 | * to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 74 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 75 | * governing permissions and limitations under the License. 76 | * 77 | * @author Rob Austin 78 | * @since 1.1 79 | */ 80 | public class ConcurrentBlockingLongQueue extends AbstractBlockingQueue { 81 | 82 | // intentionally not volatile, as we are carefully ensuring that the memory barriers are controlled below by other objects 83 | private final long[] data = new long[capacity]; 84 | 85 | /** 86 | * Creates an BlockingQueue with the default capacity of 1024 87 | */ 88 | public ConcurrentBlockingLongQueue() { 89 | 90 | } 91 | 92 | /** 93 | * @param capacity Creates an BlockingQueue with the given (fixed) capacity 94 | */ 95 | public ConcurrentBlockingLongQueue(int capacity) { 96 | super(capacity); 97 | } 98 | 99 | /** 100 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 101 | * capacity restrictions, returning true upon success and throwing an 102 | * IllegalStateException if no space is currently available. When using a capacity-restricted 103 | * queue, it is generally preferable to use {@link #offer(long) offer}. 104 | * 105 | * @param value the element to add 106 | * @return true (as specified by {@link java.util.Collection#add}) 107 | * @throws IllegalStateException if the element cannot be added at this time due to capacity 108 | * restrictions 109 | * @throws ClassCastException if the class of the specified element prevents it from being added to 110 | * this queue 111 | * @throws NullPointerException if the specified element is null 112 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 113 | * to this queue 114 | */ 115 | public boolean add(long value) { 116 | 117 | // volatile read 118 | final int writeLocation = this.producerWriteLocation; 119 | 120 | final int nextWriteLocation = getNextWriteLocationThrowIfFull(writeLocation); 121 | 122 | // purposely not volatile 123 | data[writeLocation] = value; 124 | 125 | setWriteLocation(nextWriteLocation); 126 | return true; 127 | } 128 | 129 | /** 130 | * Retrieves and removes the head of this queue, waiting if necessary until an element becomes available. 131 | *

the reads must always occur on the same thread 132 | * 133 | * @return the head of this queue 134 | * @throws InterruptedException if interrupted while waiting 135 | */ 136 | public long take() { 137 | 138 | // non volatile read ( which is quicker ) 139 | final int readLocation = this.consumerReadLocation; 140 | 141 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 142 | final int nextReadLocation = blockForReadSpace(readLocation); 143 | 144 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 145 | final long value = data[readLocation]; 146 | 147 | setReadLocation(nextReadLocation); 148 | 149 | return value; 150 | 151 | } 152 | 153 | /** 154 | * Retrieves, but does not remove, the head of this queue. 155 | * 156 | * @param timeout how long to wait before giving up, in units of unit 157 | * @param unit a TimeUnit determining how to interpret the timeout parameter 158 | * @return the head of this queue 159 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 160 | */ 161 | 162 | public long peek(long timeout, TimeUnit unit) 163 | throws InterruptedException, TimeoutException { 164 | 165 | // non volatile read ( which is quicker ) 166 | final int readLocation = this.consumerReadLocation; 167 | 168 | blockForReadSpace(timeout, unit, readLocation); 169 | 170 | // purposely not volatile as the read memory barrier occurred above when we read ' this.readLocation' 171 | return data[readLocation]; 172 | 173 | } 174 | 175 | /** 176 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 177 | * capacity restrictions, returning true upon success and false if no space is currently 178 | * available. When using a capacity-restricted queue, this method is generally preferable to {@link 179 | * #add}, which can fail to insert an element only by throwing an exception. 180 | * 181 | * @param value the element to add 182 | * @return true if the element was added to this queue, else false 183 | */ 184 | public boolean offer(long value) { 185 | 186 | // non volatile read ( which is quicker ) 187 | final int writeLocation = this.producerWriteLocation; 188 | 189 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 190 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 191 | 192 | if (nextWriteLocation == capacity - 1) { 193 | 194 | if (readLocation == 0) 195 | return false; 196 | 197 | } else if (nextWriteLocation + 1 == readLocation) 198 | return false; 199 | 200 | // purposely not volatile see the comment below 201 | data[writeLocation] = value; 202 | 203 | setWriteLocation(nextWriteLocation); 204 | return true; 205 | } 206 | 207 | /** 208 | * Inserts the specified element into this queue, waiting if necessary for space to become available. 209 | * 210 | * @param value the element to add 211 | * @throws InterruptedException if interrupted while waiting 212 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 213 | * to this queue 214 | */ 215 | public void put(long value) throws InterruptedException { 216 | 217 | final int writeLocation1 = this.producerWriteLocation; 218 | final int nextWriteLocation = blockForWriteSpace(writeLocation1); 219 | 220 | // purposely not volatile see the comment below 221 | data[writeLocation1] = value; 222 | 223 | setWriteLocation(nextWriteLocation); 224 | } 225 | 226 | /** 227 | * Inserts the specified element into this queue, waiting up to the specified wait time if necessary for 228 | * space to become available. 229 | * 230 | * @param value the element to add 231 | * @param timeout how long to wait before giving up, in units of unit 232 | * @param unit a TimeUnit determining how to interpret the timeout parameter 233 | * @return true if successful, or false if the specified waiting time elapses before 234 | * space is available 235 | * @throws InterruptedException if interrupted while waiting 236 | * @throws ClassCastException if the class of the specified element prevents it from being added to 237 | * this queue 238 | * @throws NullPointerException if the specified element is null 239 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 240 | * to this queue 241 | */ 242 | public boolean offer(long value, long timeout, TimeUnit unit) 243 | throws InterruptedException { 244 | 245 | // non volatile read ( which is quicker ) 246 | final int writeLocation = this.producerWriteLocation; 247 | 248 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 249 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 250 | 251 | 252 | if (nextWriteLocation == capacity - 1) { 253 | 254 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 255 | 256 | while (readLocation == 0) 257 | // this condition handles the case where writer has caught up with the read, 258 | // we will wait for a read, ( which will cause a change on the read location ) 259 | { 260 | if (!blockAtAdd(timeoutAt)) 261 | return false; 262 | } 263 | 264 | } else { 265 | 266 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 267 | 268 | while (nextWriteLocation == readLocation) 269 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 270 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 271 | { 272 | if (!blockAtAdd(timeoutAt)) 273 | return false; 274 | } 275 | } 276 | 277 | // purposely not volatile see the comment below 278 | data[writeLocation] = value; 279 | 280 | // the line below, is where the write memory barrier occurs, 281 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 282 | setWriteLocation(nextWriteLocation); 283 | 284 | return true; 285 | } 286 | 287 | /** 288 | * Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an 289 | * element to become available. 290 | * 291 | * @param timeout how long to wait before giving up, in units of unit 292 | * @param unit a TimeUnit determining how to interpret the timeout parameter 293 | * @return the head of this queue, or throws a TimeoutException if the specified waiting time 294 | * elapses before an element is available 295 | * @throws InterruptedException if interrupted while waiting 296 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 297 | */ 298 | public long poll(long timeout, TimeUnit unit) 299 | throws InterruptedException, TimeoutException { 300 | 301 | final int readLocation = this.consumerReadLocation; 302 | int nextReadLocation = blockForReadSpace(timeout, unit, readLocation); 303 | 304 | // purposely non volatile as the read memory barrier occurred when we read 'writeLocation' 305 | final long value = data[readLocation]; 306 | setReadLocation(nextReadLocation); 307 | 308 | return value; 309 | 310 | } 311 | 312 | /** 313 | * Returns true if this queue contains the specified element. More formally, returns 314 | * true if and only if this queue contains at least one element e such that 315 | * o.equals(e). The behavior of this operation is undefined if modified while the operation is in 316 | * progress. 317 | * 318 | * @param o object to be checked for containment in this queue 319 | * @return true if this queue contains the specified element 320 | * @throws ClassCastException if the class of the specified element is incompatible with this queue (optional) 322 | * @throws NullPointerException if the specified element is null (optional) 323 | */ 324 | public boolean contains(int o) { 325 | 326 | int readLocation = this.readLocation; 327 | int writeLocation = this.writeLocation; 328 | 329 | for (; ; ) { 330 | 331 | if (readLocation == writeLocation) 332 | return false; 333 | 334 | if (o == data[readLocation]) 335 | return true; 336 | 337 | // sets the readLocation my moving it on by 1, this may cause it it wrap back to the start. 338 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 339 | 340 | } 341 | 342 | 343 | } 344 | 345 | /** 346 | * Removes all available elements from this queue and adds them to the given array. If the target array is 347 | * smaller than the number of elements then the number of elements read will equal the capacity of the 348 | * array. This operation may be more efficient than repeatedly polling this queue. A failure encountered 349 | * while attempting to add elements to collection c may result in elements being in neither, 350 | * either or both collections when the associated exception is thrown. Further, the behavior of this 351 | * operation is undefined if the following methods are called during progress of this operation {@link 352 | * #take()}. {@link #offer(long)}, {@link #put(long)}, {@link #drainTo(long[], int)}} 353 | * 354 | * @param target the collection to transfer elements into 355 | * @return the number of elements transferred 356 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 357 | * collection 358 | * @throws ClassCastException if the class of an element of this queue prevents it from being 359 | * added to the specified collection 360 | * @throws NullPointerException if the specified collection is null 361 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 362 | * element of this queue prevents it from being added to the 363 | * specified collection 364 | */ 365 | int drainTo(long[] target) { 366 | return drainTo(target, target.length); 367 | } 368 | 369 | /** 370 | * Removes at most the given number of available elements from this queue and adds them to the given 371 | * collection. A failure encountered while attempting to add elements to collection c may result 372 | * in elements being in neither, either or both collections when the associated exception is thrown. 373 | * Attempts to drain a queue to itself result in IllegalArgumentException. Further, the behavior 374 | * of this operation is undefined if the specified collection is modified while the operation is in 375 | * progress. 376 | * 377 | * @param target the array to transfer elements into 378 | * @param maxElements the maximum number of elements to transfer 379 | * @return the number of elements transferred 380 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 381 | * collection 382 | * @throws ClassCastException if the class of an element of this queue prevents it from being 383 | * added to the specified collection 384 | * @throws NullPointerException if the specified collection is null 385 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 386 | * element of this queue prevents it from being added to the 387 | * specified collection 388 | */ 389 | int drainTo(long[] target, int maxElements) { 390 | 391 | // non volatile read ( which is quicker ) 392 | int readLocation = this.consumerReadLocation; 393 | 394 | int i = 0; 395 | 396 | // to reduce the number of volatile reads we are going to perform a kind of double check reading on the volatile write location 397 | int writeLocation = this.writeLocation; 398 | 399 | do { 400 | 401 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka capacity()=0) 402 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 403 | if (writeLocation == readLocation) { 404 | 405 | writeLocation = this.writeLocation; 406 | 407 | 408 | if (writeLocation == readLocation) { 409 | 410 | setReadLocation(readLocation); 411 | return i; 412 | } 413 | } 414 | 415 | 416 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 417 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 418 | 419 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 420 | target[i] = data[readLocation]; 421 | 422 | 423 | } while (i <= maxElements); 424 | 425 | setReadLocation(readLocation); 426 | 427 | return maxElements; 428 | } 429 | 430 | } 431 | 432 | 433 | -------------------------------------------------------------------------------- /src/main/java/uk/co/boundedbuffer/ConcurrentBlockingShortQueue.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.TimeoutException; 5 | 6 | /** 7 | * A low latency, lock free, primitive bounded blocking queue backed by an short[]. This class mimics the 8 | * interface of {@linkplain java.util.concurrent.BlockingQueue BlockingQueue}, however works with primitive 9 | * shorts rather than {@link Object}s, so is unable to actually implement the BlockingQueue .

This class 10 | * takes advantage of the Unsafe.putOrderedInt, which allows us to create non-blocking code with guaranteed 11 | * writes. These writes will not be re-ordered by instruction reordering. Under the covers it uses the faster 12 | * store-store barrier, rather than the the slower store-load barrier, which is used when doing a volatile 13 | * write. One of the trade off with this improved performance is we are limited to a single producer, single 14 | * consumer. For further information on this see, the blog post A 15 | * Faster Volatile by Rob Austin.

This queue orders elements FIFO (first-in-first-out). The 16 | * head of the queue is that element that has been on the queue the longest time. The tail 17 | * of the queue is that element that has been on the queue the shortest time. New elements are inserted at the 18 | * tail of the queue, and the queue retrieval operations obtain elements at the head of the queue.

This 19 | * is a classic "bounded buffer", in which a fixed-capacityd array holds elements inserted by 20 | * producers and extracted by consumers. Once created, the capacity cannot be changed. Attempts to {@link 21 | * #put put(e)} an element into a full queue will result in the operation blocking; attempts to {@link #take 22 | * take()} an element from an empty queue will similarly block.

Due to the lock free nature of its 23 | * implementation, ordering works on a first come first served basis.

Methods come in four forms, with 24 | * different ways of handling operations that cannot be satisfied immediately, but may be satisfied at some 25 | * point in the future: one throws an exception, the second returns a special value (either null or 26 | * false, depending on the operation), the third blocks the current thread indefinitely until the 27 | * operation can succeed, and the fourth blocks for only a given maximum time limit before giving up. These 28 | * methods are summarized in the following table: 29 | * 31 | * 33 | * 35 | * > 37 | *
Throws exception Special value Blocks Times out
Insert {@link #add add(e)} {@link #offer offer(e)} {@link #put 32 | * put(e)} {@link #offer(short, long, java.util.concurrent.TimeUnit) offer(e, time, unit)}
Remove {@link #poll poll()} not applicable {@link #take 34 | * long()} {@link #poll(long, java.util.concurrent.TimeUnit) poll(time, unit)}
Examine not applicable not applicable not 36 | * applicable {@link #peek(long, java.util.concurrent.TimeUnit) peek(time, unit)}

A uk.co.boundedbuffer.ConcurrentBlockingShortQueue is capacity bounded. At any 38 | * given time it may have a remainingCapacity beyond which no additional elements can be put 39 | * without blocking.

It is not possible to remove an arbitrary element from a queue using 40 | * remove(x). As this operation would not performed very efficiently.

All of 41 | * uk.co.boundedbuffer.ConcurrentBlockingShortQueue methods are thread-safe when used with a single 42 | * producer and single consumer, internal atomicity is achieved using lock free strategies, such as sping 43 | * locks.

Like a BlockingQueue, the uk.co.boundedbuffer.ConcurrentBlockingShortQueue does 44 | * not intrinsically support any kind of "close" or "shutdown" operation to 45 | * indicate that no more items will be added. The needs and usage of such features tend to be 46 | * implementation-dependent. For example, a common tactic is for producers to insert special 47 | * end-of-stream or poison objects, that are interpreted accordingly when taken by 48 | * consumers.

Usage example, based on a typical producer-consumer scenario. Note that a 49 | * BlockingQueue can ONLY be used with a single producers and single consumer thead. 50 | *

 51 |  *
 52 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 53 |  *
 54 |  *  public void run() {
 55 |  *       queue.put(1);
 56 |  *       }
 57 |  *  });
 58 |  *
 59 |  *
 60 |  *  Executors.newSingleThreadExecutor().execute(new Runnable() {
 61 |  *
 62 |  *  public void run() {
 63 |  *           final int value = queue.take();
 64 |  *       }
 65 |  *  });
 66 |  * 
67 | *

Memory consistency effects: As with other concurrent collections, actions in a thread prior to 68 | * placing an object into a {@code BlockingQueue} happen-before 69 | * actions subsequent to the access or removal of that element from the {@code BlockingQueue} in another 70 | * thread.

Copyright 2014 Rob Austin

Licensed under the Apache License, Version 2.0 (the 71 | * "License"); you may not use this file except in compliance with the License. You may obtain a copy of the 72 | * License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed 73 | * to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 74 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 75 | * governing permissions and limitations under the License. 76 | * 77 | * @author Rob Austin 78 | * @since 1.1 79 | */ 80 | public class ConcurrentBlockingShortQueue extends AbstractBlockingQueue { 81 | 82 | // intentionally not volatile, as we are carefully ensuring that the memory barriers are controlled below by other objects 83 | private final short[] data = new short[capacity]; 84 | 85 | 86 | /** 87 | * Creates an BlockingQueue with the default capacity of 1024 88 | */ 89 | public ConcurrentBlockingShortQueue() { 90 | 91 | } 92 | 93 | /** 94 | * @param capacity Creates an BlockingQueue with the given (fixed) capacity 95 | */ 96 | public ConcurrentBlockingShortQueue(int capacity) { 97 | super(capacity); 98 | } 99 | 100 | /** 101 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 102 | * capacity restrictions, returning true upon success and throwing an 103 | * IllegalStateException if no space is currently available. When using a capacity-restricted 104 | * queue, it is generally preferable to use {@link #offer(short) offer}. 105 | * 106 | * @param value the element to add 107 | * @return true (as specified by {@link java.util.Collection#add}) 108 | * @throws IllegalStateException if the element cannot be added at this time due to capacity 109 | * restrictions 110 | * @throws ClassCastException if the class of the specified element prevents it from being added to 111 | * this queue 112 | * @throws NullPointerException if the specified element is null 113 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 114 | * to this queue 115 | */ 116 | public boolean add(short value) { 117 | 118 | // volatile read 119 | final int writeLocation = this.producerWriteLocation; 120 | 121 | final int nextWriteLocation = getNextWriteLocationThrowIfFull(writeLocation); 122 | 123 | // purposely not volatile 124 | data[writeLocation] = value; 125 | 126 | setWriteLocation(nextWriteLocation); 127 | return true; 128 | } 129 | 130 | /** 131 | * Retrieves and removes the head of this queue, waiting if necessary until an element becomes available. 132 | *

the reads must always occur on the same thread 133 | * 134 | * @return the head of this queue 135 | * @throws InterruptedException if interrupted while waiting 136 | */ 137 | public short take() { 138 | 139 | // non volatile read ( which is quicker ) 140 | final int readLocation = this.consumerReadLocation; 141 | 142 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 143 | final int nextReadLocation = blockForReadSpace(readLocation); 144 | 145 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 146 | final short value = data[readLocation]; 147 | 148 | setReadLocation(nextReadLocation); 149 | 150 | return value; 151 | 152 | } 153 | 154 | /** 155 | * Retrieves, but does not remove, the head of this queue. 156 | * 157 | * @param timeout how long to wait before giving up, in units of unit 158 | * @param unit a TimeUnit determining how to interpret the timeout parameter 159 | * @return the head of this queue 160 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 161 | */ 162 | 163 | public short peek(long timeout, TimeUnit unit) 164 | throws InterruptedException, TimeoutException { 165 | 166 | // non volatile read ( which is quicker ) 167 | final int readLocation = this.consumerReadLocation; 168 | 169 | blockForReadSpace(timeout, unit, readLocation); 170 | 171 | // purposely not volatile as the read memory barrier occurred above when we read ' this.readLocation' 172 | return data[readLocation]; 173 | 174 | } 175 | 176 | /** 177 | * Inserts the specified element into this queue if it is possible to do so immediately without violating 178 | * capacity restrictions, returning true upon success and false if no space is currently 179 | * available. When using a capacity-restricted queue, this method is generally preferable to {@link 180 | * #add}, which can fail to insert an element only by throwing an exception. 181 | * 182 | * @param value the element to add 183 | * @return true if the element was added to this queue, else false 184 | * @throws NullPointerException if the specified element is null 185 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 186 | * to this queue 187 | */ 188 | public boolean offer(short value) { 189 | 190 | // non volatile read ( which is quicker ) 191 | final int writeLocation = this.producerWriteLocation; 192 | 193 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 194 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 195 | 196 | if (nextWriteLocation == capacity - 1) { 197 | 198 | if (readLocation == 0) 199 | return false; 200 | 201 | } else if (nextWriteLocation + 1 == readLocation) 202 | return false; 203 | 204 | // purposely not volatile see the comment below 205 | data[writeLocation] = value; 206 | 207 | setWriteLocation(nextWriteLocation); 208 | return true; 209 | } 210 | 211 | /** 212 | * Inserts the specified element into this queue, waiting if necessary for space to become available. 213 | * 214 | * @param value the element to add 215 | * @throws InterruptedException if interrupted while waiting 216 | * @throws IllegalArgumentException if some property of the specified element prevents it from being added 217 | * to this queue 218 | */ 219 | public void put(short value) throws InterruptedException { 220 | 221 | final int writeLocation1 = this.producerWriteLocation; 222 | final int nextWriteLocation = blockForWriteSpace(writeLocation1); 223 | 224 | // purposely not volatile see the comment below 225 | data[writeLocation1] = value; 226 | 227 | setWriteLocation(nextWriteLocation); 228 | } 229 | 230 | /** 231 | * Inserts the specified element into this queue, waiting up to the specified wait time if necessary for 232 | * space to become available. 233 | * 234 | * @param value the element to add 235 | * @param timeout how long to wait before giving up, in units of unit 236 | * @param unit a TimeUnit determining how to interpret the timeout parameter 237 | * @return true if successful, or false if the specified waiting time elapses before 238 | * space is available 239 | * @throws InterruptedException if interrupted while waiting 240 | */ 241 | public boolean offer(short value, long timeout, TimeUnit unit) 242 | throws InterruptedException { 243 | 244 | // non volatile read ( which is quicker ) 245 | final int writeLocation = this.producerWriteLocation; 246 | 247 | // sets the nextWriteLocation my moving it on by 1, this may cause it it wrap back to the start. 248 | final int nextWriteLocation = (writeLocation + 1 == capacity) ? 0 : writeLocation + 1; 249 | 250 | 251 | if (nextWriteLocation == capacity - 1) { 252 | 253 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 254 | 255 | while (readLocation == 0) 256 | // this condition handles the case where writer has caught up with the read, 257 | // we will wait for a read, ( which will cause a change on the read location ) 258 | { 259 | if (!blockAtAdd(timeoutAt)) 260 | return false; 261 | } 262 | 263 | } else { 264 | 265 | final long timeoutAt = System.nanoTime() + unit.toNanos(timeout); 266 | 267 | while (nextWriteLocation == readLocation) 268 | // this condition handles the case general case where the read is at the start of the backing array and we are at the end, 269 | // blocks as our backing array is full, we will wait for a read, ( which will cause a change on the read location ) 270 | { 271 | if (!blockAtAdd(timeoutAt)) 272 | return false; 273 | } 274 | } 275 | 276 | // purposely not volatile see the comment below 277 | data[writeLocation] = value; 278 | 279 | // the line below, is where the write memory barrier occurs, 280 | // we have just written back the data in the line above ( which is not require to have a memory barrier as we will be doing that in the line below 281 | setWriteLocation(nextWriteLocation); 282 | 283 | return true; 284 | } 285 | 286 | /** 287 | * Retrieves and removes the head of this queue, waiting up to the specified wait time if necessary for an 288 | * element to become available. 289 | * 290 | * @param timeout how long to wait before giving up, in units of unit 291 | * @param unit a TimeUnit determining how to interpret the timeout parameter 292 | * @return the head of this queue, or throws a TimeoutException if the specified waiting time 293 | * elapses before an element is available 294 | * @throws InterruptedException if interrupted while waiting 295 | * @throws java.util.concurrent.TimeoutException if timeout time is exceeded 296 | */ 297 | public short poll(long timeout, TimeUnit unit) 298 | throws InterruptedException, TimeoutException { 299 | 300 | final int readLocation = this.consumerReadLocation; 301 | int nextReadLocation = blockForReadSpace(timeout, unit, readLocation); 302 | 303 | // purposely non volatile as the read memory barrier occurred when we read 'writeLocation' 304 | final short value = data[readLocation]; 305 | setReadLocation(nextReadLocation); 306 | 307 | return value; 308 | 309 | } 310 | 311 | /** 312 | * Returns true if this queue contains the specified element. More formally, returns 313 | * true if and only if this queue contains at least one element e such that 314 | * o.equals(e). The behavior of this operation is undefined if modified while the operation is in 315 | * progress. 316 | * 317 | * @param o object to be checked for containment in this queue 318 | * @return true if this queue contains the specified element 319 | * @throws ClassCastException if the class of the specified element is incompatible with this queue (optional) 321 | * @throws NullPointerException if the specified element is null (optional) 322 | */ 323 | public boolean contains(int o) { 324 | 325 | int readLocation = this.readLocation; 326 | int writeLocation = this.writeLocation; 327 | 328 | for (; ; ) { 329 | 330 | if (readLocation == writeLocation) 331 | return false; 332 | 333 | if (o == data[readLocation]) 334 | return true; 335 | 336 | // sets the readLocation my moving it on by 1, this may cause it it wrap back to the start. 337 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 338 | 339 | } 340 | 341 | 342 | } 343 | 344 | /** 345 | * Removes all available elements from this queue and adds them to the given array. If the target array is 346 | * smaller than the number of elements then the number of elements read will equal the capacity of the 347 | * array. This operation may be more efficient than repeatedly polling this queue. A failure encountered 348 | * while attempting to add elements to collection c may result in elements being in neither, 349 | * either or both collections when the associated exception is thrown. Further, the behavior of this 350 | * operation is undefined if the following methods are called during progress of this operation {@link 351 | * #take()}. {@link #offer(short)}, {@link #put(short)}, {@link #drainTo(short[], int)}} 352 | * 353 | * @param target the collection to transfer elements into 354 | * @return the number of elements transferred 355 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 356 | * collection 357 | * @throws ClassCastException if the class of an element of this queue prevents it from being 358 | * added to the specified collection 359 | * @throws NullPointerException if the specified collection is null 360 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 361 | * element of this queue prevents it from being added to the 362 | * specified collection 363 | */ 364 | int drainTo(short[] target) { 365 | return drainTo(target, target.length); 366 | } 367 | 368 | /** 369 | * Removes at most the given number of available elements from this queue and adds them to the given 370 | * collection. A failure encountered while attempting to add elements to collection c may result 371 | * in elements being in neither, either or both collections when the associated exception is thrown. 372 | * Attempts to drain a queue to itself result in IllegalArgumentException. Further, the behavior 373 | * of this operation is undefined if the specified collection is modified while the operation is in 374 | * progress. 375 | * 376 | * @param target the array to transfer elements into 377 | * @param maxElements the maximum number of elements to transfer 378 | * @return the number of elements transferred 379 | * @throws UnsupportedOperationException if addition of elements is not supported by the specified 380 | * collection 381 | * @throws ClassCastException if the class of an element of this queue prevents it from being 382 | * added to the specified collection 383 | * @throws NullPointerException if the specified collection is null 384 | * @throws IllegalArgumentException if the specified collection is this queue, or some property of an 385 | * element of this queue prevents it from being added to the 386 | * specified collection 387 | */ 388 | int drainTo(short[] target, int maxElements) { 389 | 390 | // non volatile read ( which is quicker ) 391 | int readLocation = this.consumerReadLocation; 392 | 393 | int i = 0; 394 | 395 | // to reduce the number of volatile reads we are going to perform a kind of double check reading on the volatile write location 396 | int writeLocation = this.writeLocation; 397 | 398 | do { 399 | 400 | // in the for loop below, we are blocked reading unit another item is written, this is because we are empty ( aka capacity()=0) 401 | // inside the for loop, getting the 'writeLocation', this will serve as our read memory barrier. 402 | if (writeLocation == readLocation) { 403 | 404 | writeLocation = this.writeLocation; 405 | 406 | 407 | if (writeLocation == readLocation) { 408 | 409 | setReadLocation(readLocation); 410 | return i; 411 | } 412 | } 413 | 414 | 415 | // sets the nextReadLocation my moving it on by 1, this may cause it it wrap back to the start. 416 | readLocation = (readLocation + 1 == capacity) ? 0 : readLocation + 1; 417 | 418 | // purposely not volatile as the read memory barrier occurred above when we read 'writeLocation' 419 | target[i] = data[readLocation]; 420 | 421 | 422 | } while (i <= maxElements); 423 | 424 | setReadLocation(readLocation); 425 | 426 | return maxElements; 427 | } 428 | 429 | } 430 | 431 | 432 | -------------------------------------------------------------------------------- /src/test/java/uk/co/boundedbuffer/BlockingQueueTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.Queue; 7 | import java.util.concurrent.BlockingQueue; 8 | import java.util.concurrent.CountDownLatch; 9 | 10 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 11 | 12 | /** 13 | * Contains "contract" tests applicable to all BlockingQueue implementations. 14 | */ 15 | public abstract class BlockingQueueTest extends JSR166TestCase { 16 | /* 17 | * This is the start of an attempt to refactor the tests for the 18 | * various related implementations of related interfaces without 19 | * too much duplicated code. junit does not really support such 20 | * testing. Here subclasses of TestCase not only contain tests, 21 | * but also configuration information that describes the 22 | * implementation class, most importantly how to instantiate 23 | * instances. 24 | */ 25 | 26 | //---------------------------------------------------------------- 27 | // Configuration methods 28 | //---------------------------------------------------------------- 29 | 30 | /** 31 | * Returns an empty instance of the implementation class. 32 | */ 33 | protected abstract BlockingQueue emptyCollection(); 34 | 35 | /** 36 | * Returns an element suitable for insertion in the collection. 37 | * Override for collections with unusual element types. 38 | */ 39 | protected Object makeElement(int i) { 40 | return Integer.valueOf(i); 41 | } 42 | 43 | //---------------------------------------------------------------- 44 | // Tests 45 | //---------------------------------------------------------------- 46 | 47 | /** 48 | * offer(null) throws NullPointerException 49 | */ 50 | public void testOfferNull() { 51 | final Queue q = emptyCollection(); 52 | try { 53 | q.offer(null); 54 | shouldThrow(); 55 | } catch (NullPointerException success) { 56 | } 57 | } 58 | 59 | /** 60 | * add(null) throws NullPointerException 61 | */ 62 | public void testAddNull() { 63 | final Collection q = emptyCollection(); 64 | try { 65 | q.add(null); 66 | shouldThrow(); 67 | } catch (NullPointerException success) { 68 | } 69 | } 70 | 71 | /** 72 | * timed offer(null) throws NullPointerException 73 | */ 74 | public void testTimedOfferNull() throws InterruptedException { 75 | final BlockingQueue q = emptyCollection(); 76 | long startTime = System.nanoTime(); 77 | try { 78 | q.offer(null, LONG_DELAY_MS, MILLISECONDS); 79 | shouldThrow(); 80 | } catch (NullPointerException success) { 81 | } 82 | assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); 83 | } 84 | 85 | /** 86 | * put(null) throws NullPointerException 87 | */ 88 | public void testPutNull() throws InterruptedException { 89 | final BlockingQueue q = emptyCollection(); 90 | try { 91 | q.put(null); 92 | shouldThrow(); 93 | } catch (NullPointerException success) { 94 | } 95 | } 96 | 97 | /** 98 | * put(null) throws NullPointerException 99 | */ 100 | public void testAddAllNull() throws InterruptedException { 101 | final Collection q = emptyCollection(); 102 | try { 103 | q.addAll(null); 104 | shouldThrow(); 105 | } catch (NullPointerException success) { 106 | } 107 | } 108 | 109 | /** 110 | * addAll of a collection with null elements throws NullPointerException 111 | */ 112 | public void testAddAllNullElements() { 113 | final Collection q = emptyCollection(); 114 | final Collection elements = Arrays.asList(new Integer[SIZE]); 115 | try { 116 | q.addAll(elements); 117 | shouldThrow(); 118 | } catch (NullPointerException success) { 119 | } 120 | } 121 | 122 | /** 123 | * toArray(null) throws NullPointerException 124 | */ 125 | public void testToArray_NullArray() { 126 | final Collection q = emptyCollection(); 127 | try { 128 | q.toArray(null); 129 | shouldThrow(); 130 | } catch (NullPointerException success) { 131 | } 132 | } 133 | 134 | /** 135 | * drainTo(null) throws NullPointerException 136 | */ 137 | public void testDrainToNull() { 138 | final BlockingQueue q = emptyCollection(); 139 | try { 140 | q.drainTo(null); 141 | shouldThrow(); 142 | } catch (NullPointerException success) { 143 | } 144 | } 145 | 146 | /** 147 | * drainTo(this) throws IllegalArgumentException 148 | */ 149 | public void testDrainToSelf() { 150 | final BlockingQueue q = emptyCollection(); 151 | try { 152 | q.drainTo(q); 153 | shouldThrow(); 154 | } catch (IllegalArgumentException success) { 155 | } 156 | } 157 | 158 | /** 159 | * drainTo(null, n) throws NullPointerException 160 | */ 161 | public void testDrainToNullN() { 162 | final BlockingQueue q = emptyCollection(); 163 | try { 164 | q.drainTo(null, 0); 165 | shouldThrow(); 166 | } catch (NullPointerException success) { 167 | } 168 | } 169 | 170 | /** 171 | * drainTo(this, n) throws IllegalArgumentException 172 | */ 173 | public void testDrainToSelfN() { 174 | final BlockingQueue q = emptyCollection(); 175 | try { 176 | q.drainTo(q, 0); 177 | shouldThrow(); 178 | } catch (IllegalArgumentException success) { 179 | } 180 | } 181 | 182 | /** 183 | * drainTo(c, n) returns 0 and does nothing when n <= 0 184 | */ 185 | public void testDrainToNonPositiveMaxElements() { 186 | final BlockingQueue q = emptyCollection(); 187 | final int[] ns = {0, -1, -42, Integer.MIN_VALUE}; 188 | for (int n : ns) 189 | assertEquals(0, q.drainTo(new ArrayList(), n)); 190 | if (q.remainingCapacity() > 0) { 191 | // Not SynchronousQueue, that is 192 | Object one = makeElement(1); 193 | q.add(one); 194 | ArrayList c = new ArrayList(); 195 | for (int n : ns) 196 | assertEquals(0, q.drainTo(new ArrayList(), n)); 197 | assertEquals(1, q.size()); 198 | assertSame(one, q.poll()); 199 | assertTrue(c.isEmpty()); 200 | } 201 | } 202 | 203 | /** 204 | * timed poll before a delayed offer times out; after offer succeeds; 205 | * on interruption throws 206 | */ 207 | public void testTimedPollWithOffer() throws InterruptedException { 208 | final BlockingQueue q = emptyCollection(); 209 | final CheckedBarrier barrier = new CheckedBarrier(2); 210 | final Object zero = makeElement(0); 211 | Thread t = newStartedThread(new CheckedRunnable() { 212 | public void realRun() throws InterruptedException { 213 | long startTime = System.nanoTime(); 214 | assertNull(q.poll(timeoutMillis(), MILLISECONDS)); 215 | assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); 216 | 217 | barrier.await(); 218 | 219 | assertSame(zero, q.poll(LONG_DELAY_MS, MILLISECONDS)); 220 | 221 | Thread.currentThread().interrupt(); 222 | try { 223 | q.poll(LONG_DELAY_MS, MILLISECONDS); 224 | shouldThrow(); 225 | } catch (InterruptedException success) { 226 | } 227 | assertFalse(Thread.interrupted()); 228 | 229 | barrier.await(); 230 | try { 231 | q.poll(LONG_DELAY_MS, MILLISECONDS); 232 | shouldThrow(); 233 | } catch (InterruptedException success) { 234 | } 235 | assertFalse(Thread.interrupted()); 236 | } 237 | }); 238 | 239 | barrier.await(); 240 | long startTime = System.nanoTime(); 241 | assertTrue(q.offer(zero, LONG_DELAY_MS, MILLISECONDS)); 242 | assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS); 243 | 244 | barrier.await(); 245 | assertThreadStaysAlive(t); 246 | t.interrupt(); 247 | awaitTermination(t); 248 | } 249 | 250 | /** 251 | * take() blocks interruptibly when empty 252 | */ 253 | public void testTakeFromEmptyBlocksInterruptibly() { 254 | final BlockingQueue q = emptyCollection(); 255 | final CountDownLatch threadStarted = new CountDownLatch(1); 256 | Thread t = newStartedThread(new CheckedRunnable() { 257 | public void realRun() { 258 | threadStarted.countDown(); 259 | try { 260 | q.take(); 261 | shouldThrow(); 262 | } catch (InterruptedException success) { 263 | } 264 | assertFalse(Thread.interrupted()); 265 | } 266 | }); 267 | 268 | await(threadStarted); 269 | assertThreadStaysAlive(t); 270 | t.interrupt(); 271 | awaitTermination(t); 272 | } 273 | 274 | /** 275 | * take() throws InterruptedException immediately if interrupted 276 | * before waiting 277 | */ 278 | public void testTakeFromEmptyAfterInterrupt() { 279 | final BlockingQueue q = emptyCollection(); 280 | Thread t = newStartedThread(new CheckedRunnable() { 281 | public void realRun() { 282 | Thread.currentThread().interrupt(); 283 | try { 284 | q.take(); 285 | shouldThrow(); 286 | } catch (InterruptedException success) { 287 | } 288 | assertFalse(Thread.interrupted()); 289 | } 290 | }); 291 | 292 | awaitTermination(t); 293 | } 294 | 295 | /** 296 | * timed poll() blocks interruptibly when empty 297 | */ 298 | public void testTimedPollFromEmptyBlocksInterruptibly() { 299 | final BlockingQueue q = emptyCollection(); 300 | final CountDownLatch threadStarted = new CountDownLatch(1); 301 | Thread t = newStartedThread(new CheckedRunnable() { 302 | public void realRun() { 303 | threadStarted.countDown(); 304 | try { 305 | q.poll(2 * LONG_DELAY_MS, MILLISECONDS); 306 | shouldThrow(); 307 | } catch (InterruptedException success) { 308 | } 309 | assertFalse(Thread.interrupted()); 310 | } 311 | }); 312 | 313 | await(threadStarted); 314 | assertThreadStaysAlive(t); 315 | t.interrupt(); 316 | awaitTermination(t); 317 | } 318 | 319 | /** 320 | * timed poll() throws InterruptedException immediately if 321 | * interrupted before waiting 322 | */ 323 | public void testTimedPollFromEmptyAfterInterrupt() { 324 | final BlockingQueue q = emptyCollection(); 325 | Thread t = newStartedThread(new CheckedRunnable() { 326 | public void realRun() { 327 | Thread.currentThread().interrupt(); 328 | try { 329 | q.poll(2 * LONG_DELAY_MS, MILLISECONDS); 330 | shouldThrow(); 331 | } catch (InterruptedException success) { 332 | } 333 | assertFalse(Thread.interrupted()); 334 | } 335 | }); 336 | 337 | awaitTermination(t); 338 | } 339 | 340 | /** 341 | * remove(x) removes x and returns true if present 342 | * TODO: move to superclass CollectionTest.java 343 | */ 344 | public void testRemoveElement() { 345 | final BlockingQueue q = emptyCollection(); 346 | final int size = Math.min(q.remainingCapacity(), SIZE); 347 | final Object[] elts = new Object[size]; 348 | assertFalse(q.contains(makeElement(99))); 349 | assertFalse(q.remove(makeElement(99))); 350 | checkEmpty(q); 351 | for (int i = 0; i < size; i++) 352 | q.add(elts[i] = makeElement(i)); 353 | for (int i = 1; i < size; i += 2) { 354 | for (int pass = 0; pass < 2; pass++) { 355 | assertEquals((pass == 0), q.contains(elts[i])); 356 | assertEquals((pass == 0), q.remove(elts[i])); 357 | assertFalse(q.contains(elts[i])); 358 | assertTrue(q.contains(elts[i - 1])); 359 | if (i < size - 1) 360 | assertTrue(q.contains(elts[i + 1])); 361 | } 362 | } 363 | if (size > 0) 364 | assertTrue(q.contains(elts[0])); 365 | for (int i = size - 2; i >= 0; i -= 2) { 366 | assertTrue(q.contains(elts[i])); 367 | assertFalse(q.contains(elts[i + 1])); 368 | assertTrue(q.remove(elts[i])); 369 | assertFalse(q.contains(elts[i])); 370 | assertFalse(q.remove(elts[i + 1])); 371 | assertFalse(q.contains(elts[i + 1])); 372 | } 373 | checkEmpty(q); 374 | } 375 | 376 | /** 377 | * For debugging. 378 | */ 379 | public void XXXXtestFails() { 380 | fail(emptyCollection().getClass().toString()); 381 | } 382 | 383 | } 384 | -------------------------------------------------------------------------------- /src/test/java/uk/co/boundedbuffer/ConcurrentBlockingObjectQueueTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.boundedbuffer; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | 7 | import java.util.concurrent.ArrayBlockingQueue; 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | /** 14 | * Created by robaustin on 31/01/2014. 15 | */ 16 | public class ConcurrentBlockingObjectQueueTest { 17 | 18 | @Test 19 | public void testTake() throws Exception { 20 | 21 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(); 22 | 23 | // writer thread 24 | Executors.newSingleThreadExecutor().execute(new Runnable() { 25 | @Override 26 | public void run() { 27 | try { 28 | queue.put(1); 29 | } catch (InterruptedException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | }); 34 | 35 | final ArrayBlockingQueue actual = new ArrayBlockingQueue(1); 36 | 37 | // reader thread 38 | Executors.newSingleThreadExecutor().execute(new Runnable() { 39 | @Override 40 | public void run() { 41 | final int value; 42 | try { 43 | value = queue.take(); 44 | actual.add(value); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } 48 | 49 | } 50 | }); 51 | 52 | final Integer value = actual.poll(1, TimeUnit.SECONDS); 53 | Assert.assertEquals((int) value, 1); 54 | Thread.sleep(100); 55 | } 56 | 57 | 58 | @Test 59 | public void testWrite() throws Exception { 60 | 61 | } 62 | 63 | @Test 64 | public void testRead() throws Exception { 65 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(); 66 | queue.put(10); 67 | final int value = queue.take(); 68 | junit.framework.Assert.assertEquals(10, value); 69 | } 70 | 71 | @Test 72 | public void testRead2() throws Exception { 73 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(); 74 | queue.put(10); 75 | queue.put(11); 76 | final int value = queue.take(); 77 | junit.framework.Assert.assertEquals(10, value); 78 | final int value1 = queue.take(); 79 | junit.framework.Assert.assertEquals(11, value1); 80 | } 81 | 82 | @Test 83 | public void testReadLoop() throws Exception { 84 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(); 85 | 86 | for (int i = 1; i < 50; i++) { 87 | queue.put(i); 88 | final int value = queue.take(); 89 | junit.framework.Assert.assertEquals(i, value); 90 | } 91 | } 92 | 93 | /** 94 | * reader and add, reader and writers on different threads 95 | * 96 | * @throws Exception 97 | */ 98 | @Test 99 | public void testWithFasterReader() throws Exception { 100 | 101 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(); 102 | final int max = 100; 103 | final CountDownLatch countDown = new CountDownLatch(1); 104 | 105 | final AtomicBoolean success = new AtomicBoolean(true); 106 | 107 | 108 | new Thread( 109 | new Runnable() { 110 | 111 | @Override 112 | public void run() { 113 | for (int i = 1; i < max; i++) { 114 | try { 115 | queue.put(i); 116 | Thread.sleep((int) (Math.random() * 100)); 117 | } catch (InterruptedException e) { 118 | e.printStackTrace(); 119 | } 120 | } 121 | 122 | } 123 | }).start(); 124 | 125 | 126 | new Thread( 127 | new Runnable() { 128 | 129 | @Override 130 | public void run() { 131 | 132 | int value = 0; 133 | for (int i = 1; i < max; i++) { 134 | try { 135 | 136 | final int newValue = queue.take(); 137 | 138 | junit.framework.Assert.assertEquals(i, newValue); 139 | 140 | 141 | if (newValue != value + 1) { 142 | success.set(false); 143 | return; 144 | } 145 | 146 | value = newValue; 147 | 148 | 149 | Thread.sleep((int) (Math.random() * 10)); 150 | } catch (InterruptedException e) { 151 | e.printStackTrace(); 152 | } 153 | } 154 | countDown.countDown(); 155 | 156 | } 157 | }).start(); 158 | 159 | countDown.await(); 160 | 161 | Assert.assertTrue(success.get()); 162 | } 163 | 164 | 165 | /** 166 | * faster writer 167 | * 168 | * @throws Exception 169 | */ 170 | @Test 171 | public void testWithFasterWriter() throws Exception { 172 | 173 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(); 174 | final int max = 200; 175 | final CountDownLatch countDown = new CountDownLatch(1); 176 | final AtomicBoolean success = new AtomicBoolean(true); 177 | 178 | new Thread( 179 | new Runnable() { 180 | 181 | @Override 182 | public void run() { 183 | for (int i = 1; i < max; i++) { 184 | try { 185 | queue.put(i); 186 | 187 | Thread.sleep((int) (Math.random() * 3)); 188 | } catch (InterruptedException e) { 189 | e.printStackTrace(); 190 | } 191 | } 192 | 193 | } 194 | }).start(); 195 | 196 | 197 | new Thread( 198 | new Runnable() { 199 | 200 | @Override 201 | public void run() { 202 | 203 | int value = 0; 204 | for (int i = 1; i < max; i++) { 205 | 206 | try { 207 | final int newValue = queue.take(); 208 | 209 | junit.framework.Assert.assertEquals(i, newValue); 210 | 211 | 212 | if (newValue != value + 1) { 213 | success.set(false); 214 | return; 215 | } 216 | 217 | value = newValue; 218 | 219 | 220 | Thread.sleep((int) (Math.random() * 10)); 221 | } catch (InterruptedException e) { 222 | e.printStackTrace(); 223 | } 224 | } 225 | countDown.countDown(); 226 | 227 | } 228 | }).start(); 229 | 230 | countDown.await(); 231 | Assert.assertTrue(success.get()); 232 | } 233 | 234 | 235 | @Test 236 | @Ignore 237 | public void testFlatOut() throws Exception { 238 | 239 | testConcurrentBlockingObjectQueue(Integer.MAX_VALUE); 240 | 241 | } 242 | 243 | private void testConcurrentBlockingObjectQueue(final int nTimes) throws InterruptedException { 244 | final ConcurrentBlockingObjectQueue queue = new ConcurrentBlockingObjectQueue(1024); 245 | final CountDownLatch countDown = new CountDownLatch(1); 246 | 247 | final AtomicBoolean success = new AtomicBoolean(true); 248 | 249 | Thread writerThread = new Thread( 250 | new Runnable() { 251 | 252 | @Override 253 | public void run() { 254 | try { 255 | for (int i = 1; i < nTimes; i++) { 256 | queue.put(i); 257 | 258 | } 259 | 260 | } catch (Throwable e) { 261 | e.printStackTrace(); 262 | } 263 | 264 | } 265 | }); 266 | 267 | 268 | writerThread.setName("ConcurrentBlockingObjectQueue-writer"); 269 | 270 | Thread readerThread = new Thread( 271 | new Runnable() { 272 | 273 | @Override 274 | public void run() { 275 | 276 | int value = 0; 277 | for (int i = 1; i < nTimes; i++) { 278 | 279 | final int newValue; 280 | try { 281 | newValue = queue.take(); 282 | 283 | 284 | if (newValue != value + 1) { 285 | success.set(false); 286 | return; 287 | } 288 | 289 | value = newValue; 290 | } catch (InterruptedException e) { 291 | e.printStackTrace(); 292 | } 293 | 294 | } 295 | countDown.countDown(); 296 | 297 | } 298 | }); 299 | 300 | readerThread.setName("ConcurrentBlockingObjectQueue-reader"); 301 | 302 | writerThread.start(); 303 | readerThread.start(); 304 | 305 | countDown.await(); 306 | 307 | writerThread.stop(); 308 | readerThread.stop(); 309 | } 310 | 311 | 312 | private void testArrayBlockingQueue(final int nTimes) throws InterruptedException { 313 | 314 | final ArrayBlockingQueue queue = new ArrayBlockingQueue(1024); 315 | final CountDownLatch countDown = new CountDownLatch(1); 316 | 317 | final AtomicBoolean success = new AtomicBoolean(true); 318 | 319 | Thread writerThread = new Thread( 320 | new Runnable() { 321 | 322 | @Override 323 | public void run() { 324 | try { 325 | for (int i = 1; i < nTimes; i++) { 326 | queue.put(i); 327 | 328 | } 329 | 330 | } catch (Throwable e) { 331 | e.printStackTrace(); 332 | } 333 | 334 | } 335 | }); 336 | 337 | 338 | writerThread.setName("ArrayBlockingQueue-writer"); 339 | 340 | Thread readerThread = new Thread( 341 | new Runnable() { 342 | 343 | @Override 344 | public void run() { 345 | 346 | int value = 0; 347 | for (int i = 1; i < nTimes; i++) { 348 | 349 | final int newValue; 350 | try { 351 | newValue = queue.take(); 352 | } catch (InterruptedException e) { 353 | e.printStackTrace(); 354 | return; 355 | } 356 | 357 | 358 | if (newValue != value + 1) { 359 | success.set(false); 360 | return; 361 | } 362 | 363 | value = newValue; 364 | 365 | } 366 | countDown.countDown(); 367 | 368 | } 369 | }); 370 | 371 | readerThread.setName("ArrayBlockingQueue-reader"); 372 | 373 | writerThread.start(); 374 | readerThread.start(); 375 | 376 | countDown.await(); 377 | 378 | writerThread.stop(); 379 | readerThread.stop(); 380 | } 381 | 382 | 383 | @Test 384 | public void testLatency() throws NoSuchFieldException, InterruptedException { 385 | 386 | 387 | for (int pwr = 2; pwr < 20; pwr++) { 388 | int i = (int) Math.pow(2, pwr); 389 | 390 | 391 | final long arrayBlockingQueueStart = System.nanoTime(); 392 | testArrayBlockingQueue(i); 393 | final double arrayBlockingDuration = System.nanoTime() - arrayBlockingQueueStart; 394 | 395 | 396 | final long queueStart = System.nanoTime(); 397 | testConcurrentBlockingObjectQueue(i); 398 | final double concurrentBlockingDuration = System.nanoTime() - queueStart; 399 | 400 | System.out.printf("Performing %,d loops, ArrayBlockingQueue() took %.3f ms and calling ConcurrentBlockingObjectQueue took %.3f ms on average, ratio=%.1f%n", 401 | i, arrayBlockingDuration / 1000000.0, concurrentBlockingDuration / 1000000.0, (double) arrayBlockingDuration / (double) concurrentBlockingDuration); 402 | /** 403 | System.out.printf("%d\t%.3f\t%.3f\n", 404 | i, arrayBlockingDuration / 1000000.0, concurrentBlockingDuration / 1000000.0, (double) arrayBlockingDuration / (double) concurrentBlockingDuration); 405 | **/ 406 | } 407 | 408 | 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /src/test/resources/performance-comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobAustin/low-latency-primitive-concurrent-queues/057759d1f619b914316e069541edb8a7dd974d8f/src/test/resources/performance-comparison.png --------------------------------------------------------------------------------