Java 非阻塞方式提供+;用于阻塞队列(或其他内容)的drainTo
我正在寻找一些看似简单的东西,一个带有非阻塞版本“add”和“drain”的集合。大概是这样的:Java 非阻塞方式提供+;用于阻塞队列(或其他内容)的drainTo,java,concurrency,Java,Concurrency,我正在寻找一些看似简单的东西,一个带有非阻塞版本“add”和“drain”的集合。大概是这样的: List itemsToProcess = queue.addOrDrainAndAdd( item ); if ( itemsToProcess != null ) process( items ); 在我看来,如果我把这些作为单独的“要约”和“拖入”电话,我可能会在第一次打电话之前打两次要约。我还需要一个类似于“while(!queue.offer(item))”的循环,这样在它被耗尽
List itemsToProcess = queue.addOrDrainAndAdd( item );
if ( itemsToProcess != null )
process( items );
在我看来,如果我把这些作为单独的“要约”和“拖入”电话,我可能会在第一次打电话之前打两次要约。我还需要一个类似于“while(!queue.offer(item))”的循环,这样在它被耗尽之后,提供就可以工作了,我想这还需要我检查drain是否返回一个空集合(因为两个可能会调用drain)。我的天真实现是这样的,但它似乎不是最优的:
void addBatchItem( T item ) {
while ( !batch.offer( item ) ) {
List<T> batched = new ArrayList<>( batchSize );
batch.drainTo( batched );
process( batched );
}
}
void addBatchItem(T项){
而(!批处理报价(项目)){
列表批处理=新的ArrayList(batchSize);
分批排放(分批);
工艺(分批);
}
}
然后我想也许有更好的方法,我只是不知道。谢谢
编辑:
好的,这里有一个解决方案(基于ArrayBlockingQueue的阻塞):
public void add(T batchItem){
而(!batch.offer(batchItem)){
冲洗();
}
}
公共图书馆{
列表批处理=新的ArrayList(batchSize);
批量排放(批量、批量大小);
如果(!batched.isEmpty())
executor.execute(新阶段未知(批处理));
}
我想我的问题是,鉴于ConcurrentLinkedQueue需要为每个节点分配对象,因此上述解决方案是否比基于ConcurrentLinkedQueue的解决方案更适合此目的
示例类及其用法:
public abstract class Batcher<T> {
private final int batchSize;
private ArrayBlockingQueue<T> batch;
private ExecutorService executor;
private final Phaser phaser = new Phaser( 1 );
public Batcher( int batchSize, ExecutorService executor ) {
this.batchSize = batchSize;
this.executor = executor;
this.batch = new ArrayBlockingQueue<>( batchSize );
}
public void add( T batchItem ) {
while ( !batch.offer( batchItem ) ) {
flush();
}
}
public void flush() {
List<T> batched = new ArrayList<>( batchSize );
batch.drainTo( batched, batchSize );
if ( !batched.isEmpty() )
executor.execute( new PhasedRunnable( batched ) );
}
public abstract void onFlush( List<T> batch );
public void awaitDone() {
phaser.arriveAndAwaitAdvance();
}
public void awaitDone( long duration, TimeUnit unit ) throws TimeoutException {
try {
phaser.awaitAdvanceInterruptibly( phaser.arrive(), duration, unit );
}
catch ( InterruptedException e ) {
Thread.currentThread().interrupt();
}
}
private class PhasedRunnable implements Runnable {
private final List<T> batch;
private PhasedRunnable( List<T> batch ) {
this.batch = batch;
phaser.register();
}
@Override
public void run() {
try {
onFlush( batch );
}
finally {
phaser.arrive();
}
}
}
}
公共抽象类批处理程序{
私有最终整数批量大小;
私有ArrayBlockingQueue批处理;
私人遗嘱执行人;
专用最终相位器相位器=新相位器(1);
公共批处理程序(int batchSize,executor服务executor){
this.batchSize=batchSize;
this.executor=执行人;
this.batch=新的ArrayBlockingQueue(batchSize);
}
公共无效添加(T批处理项){
而(!batch.offer(batchItem)){
冲洗();
}
}
公共图书馆{
列表批处理=新的ArrayList(batchSize);
批量排放(批量、批量大小);
如果(!batched.isEmpty())
executor.execute(新阶段未知(批处理));
}
公开摘要作废(列表批量);
公众假期(已完成){
相位器。到达并等待前进();
}
public void waitdone(长持续时间,时间单位)抛出TimeoutException{
试一试{
相位器提前中断等待(相位器到达(),持续时间,单位);
}
捕捉(中断异常e){
Thread.currentThread().interrupt();
}
}
私有类PhasedRunnable实现可运行{
私人最终名单批次;
private PhasedRunnable(列表批处理){
this.batch=批次;
相量寄存器();
}
@凌驾
公开募捐{
试一试{
冲洗(批量);
}
最后{
相位器到达();
}
}
}
}
这是一个简单的示例,更完整的示例可能是JPA实体更新或插入。此外,我希望能够同时调用#add
@Test
public void testOddNumber() {
Batcher<Integer> batcher = new Batcher<Integer>( 10, executor ) {
@Override
public void onFlush( List<Integer> batch ) {
count.addAndGet( batch.size() );
}
};
for ( int at = 0; at != 21; ++at ) {
batcher.add( at );
}
batcher.flush();
batcher.awaitDone();
assertEquals( count.get(), 21 );
}
@测试
公共无效testOddNumber(){
配料器配料器=新配料器(10,执行器){
@凌驾
公共作废onFlush(列表批处理){
count.addAndGet(batch.size());
}
};
for(int at=0;at!=21;++at){
配料器添加(at);
}
batcher.flush();
batcher.waitdone();
assertEquals(count.get(),21);
}
看起来很简单,一个集合有一个非阻塞但原子版本的“add”和“drain”
这实际上是不可能的。非阻塞算法(在1-CAS ARCH上)针对原子性在单个内存地址上工作。因此,在不阻塞和原子化的情况下排空整个队列是不可能的
编辑:
根据您的编辑,我认为这可能是实现所需目标的最有效方法。因此,您之所以要这样做,是因为队列已满,对吗?在您的另一条评论中,您说您实际上并不关心它是否是原子的:什么,关键到底是什么?非阻塞添加和排放…什么?你是对的。我不在乎它本身是原子的。我关心的是我一次只消耗N个项目。如果在排放时将一些添加到队列中也没关系,因为我可以控制排放的最大项目数。这更有意义吗?在他试图实现的特殊情况下,我不明白为什么不可能:例如:假设他想要添加的项目和队列中的项目都在一个链表中:实现不能自动交换两个列表的头指针吗?@user1888440 Ok,这与非阻塞式排水管略有不同。例如,BlockingQueue的
drainTo
将导致在启动时完全耗尽状态的集合。在您的情况下,您的实现会很好地工作,并且可能比其他任何东西都更优化。另一种方法是使用ConcurrentLinkedQueue作为真正的非阻塞队列。@JVMATL,除非我没有正确理解您。您只能将一个地址与另一个地址交换,因此A->B,而不是A->&&B->A。这需要一个2-CAS(标准JDK正在开发中)@JVMATL,您可以在谷歌上进行多字比较和交换。它至少在最近的JDK9预备课程中被提到,以添加到JDK中。
@Test
public void testOddNumber() {
Batcher<Integer> batcher = new Batcher<Integer>( 10, executor ) {
@Override
public void onFlush( List<Integer> batch ) {
count.addAndGet( batch.size() );
}
};
for ( int at = 0; at != 21; ++at ) {
batcher.add( at );
}
batcher.flush();
batcher.awaitDone();
assertEquals( count.get(), 21 );
}