我们正在开发一个应用程序,通过接收来自3个不同来源的消息,可以影响一组对象。每条消息(来自任何来源)都有一个对象作为其目标。每个消息接收器将在其自己的线程上运行。
我们希望消息的处理(在接收之后)尽可能高速,因此针对目标对象的消息处理将使用来自线程池的另一个线程来完成。处理消息所需的时间比读取/接收来自发件人的消息要长。
我认为如果池中的每个线程仅专用于特定的一组对象,它会更快,例如:
Thread1 -> objects named A-L
Thread2 -> objects named M-Z
每组对象(或线程)都有一个专用的消息队列待处理。
我的假设是,如果所需的唯一线程同步是在每个接收线程和一个处理线程之间,在需要将消息放入阻塞队列的持续时间内,它将比随机分配工作线程更快处理消息(在这种情况下,可能有2个不同的线程与同一对象的消息)。
我的问题实际上是两部分:
人们是否同意专用工作线程的假设 对一组特定的对象是更好/更快的方法?
假设这是一种更好的方法,请执行现有的Java ThreadPool 有没有办法支持这个?或者它需要我们编码 我们自己的ThreadPool实现?
感谢您提出的任何建议。
答案 0 :(得分:7)
我假设总体目标是尝试最大化这些入站消息的并发处理。您有来自3个来源的接收器,需要将消息放入池中,以便进行最佳处理。因为来自3个来源中的任何一个的消息可能处理同时处理不能的同一目标对象,所以您希望将消息分开,以便可以同时处理它们,但前提是它们不能保证不被同时处理引用相同的目标对象。
我会在目标对象上实现hashCode()
方法(可能只是name.hashCode()
),然后使用该值将对象放入BlockingQueue
s的数组中,每个都有一个线程消耗它们。使用Executors.newSingleThreadExecutor()
数组就可以了。通过队列数修改哈希值模式并将其放入该队列中。您需要预先定义最大的处理器数量。取决于CPU处理的密集程度。
所以类似下面的代码应该有效:
private static final int NUM_PROCESSING_QUEUES = 6;
...
ExecutorService[] pools = new ExecutorService[NUM_PROCESSING_QUEUES];
for (int i = 0; i < pools.length; i++) {
pools[i] = Executors.newSingleThreadExecutor();
}
...
// receiver loop:
while (true) {
Message message = receiveMessage();
int hash = Math.abs(message.hashCode());
// put each message in the appropriate pool based on its hash
// this assumes message is runnable
pools[hash % pools.length].submit(message);
}
此机制的一个好处是您可以限制目标对象的同步。您知道只有一个线程才会更新相同的目标对象。
人们是否同意将工作线程专用于特定对象集的假设是更好/更快的方法?
是。这似乎是获得最佳并发性的正确方法。
假设这是一种更好的方法,现有的Java ThreadPool类是否有办法支持这一点?或者它是否要求我们编写自己的ThreadPool实现?
我不知道任何完成此操作的线程池。但是我会不编写自己的实现。只需使用它们就像上面的代码大纲一样。
答案 1 :(得分:3)
一般来说,这样的方法是个坏主意。它属于“don't optimize early”口头禅。
此外,如果实施,您的想法可能损害您的表现,而不是帮助它。一个简单的例子说明,如果你突然在一种类型上获得大量请求,那么它将无法正常工作 - 另一个工作线程将处于空闲状态。
最好的方法是使用标准的生产者 - 消费者模式,并通过各种负载下的系统测试来调整消费者线程的数量 - 最好是通过记录现实生活中的交易。
这些情况的“转到”框架是来自java.util.concurrent
包的类。我建议使用从BlockingQueue
工厂方法之一创建的ArrayBlockingQueue
(可能是ExecutorService
)Executors
,可能是newCachedThreadPool()
。
一旦您实施并进行了系统测试,如果您发现经过验证的性能问题,那么请分析您的系统,找到瓶颈并进行修复。
你不应该提前优化的原因是大多数时候问题不在你期望的地方
答案 2 :(得分:0)
作为替代方法:我建议使用现有框架,例如RabbitMQ或ActiveMQ。尝试发明自己的消息传递框架可能是一个挑战。如果您尝试使用自己的框架增加价值,那就是一件事。如果你只是需要一个来实现你的目标,那就是另一个。这些框架提供了许多最佳消息传递选项,值得考虑。
答案 3 :(得分:0)
我的回答是:
一些解释:
我不认为这些前提与ThreadPool的目的相矛盾,后者只是将任务与线程相关联。但是在这个模型中,Threadpool只会将线程与任务关联一次,然后线程将继续运行以轮询其输入消息队列。
线程的摩擦点应该是中间消息队列,也可能是与处理这些消息相关的其他资源。根据您的解释,我认为您计划通过巧妙地将消息处理划分为任务来将第二类减少到最小。每个队列只能由分区任务和与队列关联的处理任务访问,因此它应该是最小的。
答案 4 :(得分:0)
您应该能够为ThreadPoolExecutor提供特殊的BlockingQueue。队列会记住哪个线程正在处理哪种类型的消息,以便它可以保留所有相同类型的消息。
MyQueue
ownership relation of thread - msgType
take/poll()
if current thread owns msg type X
if there is a message of type X
return that message
else
give up ownership
// current thread does not own any message type
if there is a messsage of type Y, Y is not owned by any thread
current thread owns Y
return that message
// there's no message belonging to an unowned type
wait then retry