Java 非阻塞方式提供+;用于阻塞队列(或其他内容)的drainTo

Java 非阻塞方式提供+;用于阻塞队列(或其他内容)的drainTo,java,concurrency,Java,Concurrency,我正在寻找一些看似简单的东西,一个带有非阻塞版本“add”和“drain”的集合。大概是这样的: List itemsToProcess = queue.addOrDrainAndAdd( item ); if ( itemsToProcess != null ) process( items ); 在我看来,如果我把这些作为单独的“要约”和“拖入”电话,我可能会在第一次打电话之前打两次要约。我还需要一个类似于“while(!queue.offer(item))”的循环,这样在它被耗尽

我正在寻找一些看似简单的东西,一个带有非阻塞版本“add”和“drain”的集合。大概是这样的:

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 );
}