Java 具有唯一任务的线程池队列

Java 具有唯一任务的线程池队列,java,multithreading,spring,java.util.concurrent,Java,Multithreading,Spring,Java.util.concurrent,我使用(spring)来异步执行一些任务 所需的任务将从外部数据库将某些对象加载到我的系统内存中。 我使用的最大线程池大小为10,最大队列大小为100 假设所有10个线程都被占用从my DB获取对象,并且创建了一个任务,它将进入队列。现在创建了另一个任务,该任务应该从DB中获取相同的对象(DB中的相同键),它还将进入队列(假设所有10个线程仍然被占用) 因此,我的队列可能很容易被重复的任务填满,这些任务将依次执行,我不希望这种情况发生 我认为解决方案应该以一个唯一的集合的形式出现,该集合充当线程

我使用(spring)来异步执行一些任务

所需的任务将从外部数据库将某些对象加载到我的系统内存中。 我使用的最大线程池大小为10,最大队列大小为100

假设所有10个线程都被占用从my DB获取对象,并且创建了一个任务,它将进入队列。现在创建了另一个任务,该任务应该从DB中获取相同的对象(DB中的相同键),它还将进入队列(假设所有10个线程仍然被占用)

因此,我的队列可能很容易被重复的任务填满,这些任务将依次执行,我不希望这种情况发生

我认为解决方案应该以一个唯一的集合的形式出现,该集合充当线程池队列。 在后台,ThreadPoolTaskExecutor使用LinkedBlockingQueue,它不提供唯一性

我想到了一些可能的解决方案,但没有一个能让我满意:

  • 使用而不是ThreadPoolTaskExecutor。ThreadPoolExecutor提供了一个构造函数,可以让我确定线程池队列类型,但它需要实现BlockingQueue接口。我找不到保持唯一性的实现
这使我尝试扩展并覆盖添加:

public boolean add(E e)
    if(!this.contains(e)) {
        return super.add(e);
    } else {
        return false;
    }
}
但据我所知,这将导致性能大幅下降,因为
contains
方法受到O(n)-坏主意的限制

什么能解决我的问题?我的目标是获得良好的性能(在内存性能权衡的情况下,我不介意为性能而放弃内存)。

使用,您可以做类似的事情(尚未测试)

Set uniqueQueue=Sets.newConcurrentHashSet();
ThreadPoolExecutor ThreadPoolExecutor=新的ThreadPoolExecutor(10,10,0,TimeUnit.SECONDS,Queues.newLinkedBlockingQueue(100));
ListingExecutorService executorService=MoreExecutors.ListingDecorator(threadPoolExecutor);
字符串t1=“abc”;
if(uniqueQueue.add(t1)){
ListenableFuture=executorService.submit(()->“使用”+t1做点什么”;
Futures.addCallback(future,newfuturecallback(){
@凌驾
成功时的公共void(字符串结果){
uniqueQueue.remove(t1);
}
@凌驾
失效时的公共无效(可丢弃的t){
uniqueQueue.remove(t1);
}
});
}
导致

  • 只有当前未处理或队列中未包含的项目才会添加到队列中(
    uniquequequeue
  • 已处理的项目将从
    唯一队列中删除
  • 队列中最多只能有100个项目
此实现不处理

  • submit()
    方法引发的异常
  • unqiueQueue中的最大项目数
关于将对象从数据库加载到内存中的需求,您可能需要了解一下

更新

 public interface RunnableWithId extends Runnable {

    /**
     * @return A unique id for this task
     */
    String getTaskId();
}

如果允许您管理数据库,我建议您使用数据库本身以防止重复工作:

  • 将lockid列添加到表中
  • 向表中添加状态列(可能是“新建”和“完成”)
  • 确保您的数据库隔离级别至少为READ_COMMITTED
然后在主线程中尝试以下操作:

Random rand = new Random();
int lockId = rand.nextInt(Integer.MAX_VALUE - 1) + 1;
String update = "UPDATE DB.Table SET lockid=" + lockId + " WHERE lockid=0 AND status='new' " // + AND your conditions + LIMIT ##
String select = "SELECT * FROM DB.Table WHERE lockid=" + lockId;
// now execute those sql statements with QueryRunner or whatever you use in-house
从select返回的行是您添加到队列中的行

然后,您就有了一个类,该类通过从队列中检索这些行来实现处理这些行的Runnable。一旦它处理了一行,您将执行另一个SQL更新(在Runnable内部)以将lockId设置回零,并将状态设置为“完成”


即使您有多台机器,每台机器都有自己的队列,这种方法也可以工作。

一种类似于公认的解决方案但基于Spring(与Guava相反)的解决方案:

创建界面Runnableithid

 public interface RunnableWithId extends Runnable {

    /**
     * @return A unique id for this task
     */
    String getTaskId();
}
使用IDEXecutor创建另一个界面任务

import org.springframework.core.task.TaskExecutor;


public interface TaskWithIdExecutor extends TaskExecutor {

    /**
     * Executes the given task if it is not queued or already running
     *
     * @param task The task to execute
     */
    void executeIfNotQueuedOrRunningAlready(RunnableWithId task);
}
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;

import java.util.Set;

/**
 * In addition to all the abilities of ThreadPoolTaskExecutor adds the ability
 * to execute a task only if it is not already running/queued using the
 * executeIfNotQueuedOrRunningAlready method.
 *
 * @see ThreadPoolTaskExecutor
 */
public class UniquTaskExecutor extends ThreadPoolTaskExecutor implements TaskWithIdExecutor {

    private Set<String> queuedTasks;

    public UniquTaskExecutor() {
        queuedTasks = Sets.newConcurrentHashSet();
    }

    @Override
    public void execute(Runnable task) {
        super.execute(task);
    }

    /**
     * @param task The task to execute
     */
    @Override
    public void executeIfNotQueuedOrRunningAlready(RunnableWithId task) {
        if (queuedTasks.add(task.getTaskId())) {
            ListenableFuture<?> res = submitListenable(task);
            res.addCallback(new ListenableFutureCallback<Object>() {
                @Override
                public void onFailure(Throwable throwable) {
                    queuedTasks.remove(task.getTaskId());
                }

                @Override
                public void onSuccess(Object o) {
                    queuedTasks.remove(task.getTaskId());
                }
            });
        }
    }
}
创建自定义执行器UniquTaskExecutor

import org.springframework.core.task.TaskExecutor;


public interface TaskWithIdExecutor extends TaskExecutor {

    /**
     * Executes the given task if it is not queued or already running
     *
     * @param task The task to execute
     */
    void executeIfNotQueuedOrRunningAlready(RunnableWithId task);
}
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;

import java.util.Set;

/**
 * In addition to all the abilities of ThreadPoolTaskExecutor adds the ability
 * to execute a task only if it is not already running/queued using the
 * executeIfNotQueuedOrRunningAlready method.
 *
 * @see ThreadPoolTaskExecutor
 */
public class UniquTaskExecutor extends ThreadPoolTaskExecutor implements TaskWithIdExecutor {

    private Set<String> queuedTasks;

    public UniquTaskExecutor() {
        queuedTasks = Sets.newConcurrentHashSet();
    }

    @Override
    public void execute(Runnable task) {
        super.execute(task);
    }

    /**
     * @param task The task to execute
     */
    @Override
    public void executeIfNotQueuedOrRunningAlready(RunnableWithId task) {
        if (queuedTasks.add(task.getTaskId())) {
            ListenableFuture<?> res = submitListenable(task);
            res.addCallback(new ListenableFutureCallback<Object>() {
                @Override
                public void onFailure(Throwable throwable) {
                    queuedTasks.remove(task.getTaskId());
                }

                @Override
                public void onSuccess(Object o) {
                    queuedTasks.remove(task.getTaskId());
                }
            });
        }
    }
}
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
导入org.springframework.util.concurrent.ListenableFuture;
导入org.springframework.util.concurrent.ListenableFutureCallback;
导入java.util.Set;
/**
*除了ThreadPoolTaskExecutor的所有功能外,它还添加了以下功能
*仅在任务尚未运行/排队时使用
*ExecuteIfNotQueuedRunningReady方法。
*
*@请参阅ThreadPoolTaskExecutor
*/
公共类UniquTaskExecutor扩展ThreadPoolTaskExecutor使用IDEXecutor实现任务{
私有集合排队任务;
公共UniquTaskExecutor(){
queuedTasks=Sets.newConcurrentHashSet();
}
@凌驾
公共void执行(可运行任务){
超级执行(任务);
}
/**
*@param task要执行的任务
*/
@凌驾
public void executeifnotqueuedorunningready(runnableiid任务){
if(queuedTasks.add(task.getTaskId())){
ListenableFuture res=submitListenable(任务);
res.addCallback(新ListenableFutureCallback(){
@凌驾
失败时的公共无效(可丢弃){
queuedTasks.remove(task.getTaskId());
}
@凌驾
成功时的公共无效(对象o){
queuedTasks.remove(task.getTaskId());
}
});
}
}
}

使用UniquTaskExecutor的ExecuteIfNotQueuedoRunningEarly方法来实现任务执行的唯一性。

我已经考虑过使用一种与您的建议或多或少类似的解决方法(使用附加集强制唯一性)。如果在接下来的几天里我得不到更好的答案,我会接受你的答案。我刚刚找到了一个BlockingQueue工具