我有一个用例,其中有一组项目DiagnosticRun
已提交到集群。我想按顺序处理它们(以避免冲突)。我正在尝试使用受Queue
保护的Hazelcast Lock
,以确保一次处理一个项目。 Hazelcast在我的群集中以嵌入式模式运行。如果我在ItemListener
中注册了Queue
,从take()
方法内部调用Queue
上的itemAdded()
是否安全?例如:
@Component
public class DistributedQueueListener
{
public static final String DIAGNOSTICS_RUN_QUEUE_NAME = "diagnosticRun";
@Autowired
private HazelcastInstance hazelcast;
@Autowired
private ProductVersioningService productVersioningService;
private IQueue<DiagnosticRun> diagnosticRunQueue;
private ILock diagnosticRunLock;
private String diagnosticRunListenerId;
@PostConstruct
public void init()
{
diagnosticRunQueue = hazelcast.getQueue(DIAGNOSTICS_RUN_QUEUE_NAME);
diagnosticRunLock = hazelcast.getLock("diagnosticRunLock");
diagnosticRunListenerId = diagnosticRunQueue.addItemListener(new DiagnosticRunListener(), false);
}
@PreDestroy
public void stop()
{
diagnosticRunQueue.removeItemListener(diagnosticRunListenerId);
}
public class DiagnosticRunListener implements ItemListener<DiagnosticRun>
{
@Override
public void itemAdded(ItemEvent<diagnosticRun> item)
{
diagnosticRunLock.lock(5, TimeUnit.SECONDS);
try
{
DiagnosticRun diagnosticRun = diagnosticRunQueue.poll();
if(diagnosticRun != null)
{
productVersioningService.updateProductDeviceTable(diagnosticRun);
}
}
finally
{
diagnosticRunLock.unlock();
}
}
@Override
public void itemRemoved(ItemEvent<diagnosticRun> item)
{
}
}
}
我不确定从该位置和线程在take()
上调用Queue
是否是线程安全的。
如果不允许这样做,我将不得不建立自己的长时间运行循环以poll()
到Queue
。我不确定在Spring Boot应用程序中设置长时间运行的线程的最佳方法是什么。假设上面的方法不起作用,下面的代码是否是线程安全的?还是有更好的方法来做到这一点?
@Component
public class DistributedQueueListener
{
public static final String DIAGNOSTIC_RUN_QUEUE_NAME = "diagnosticRun";
@Autowired
private HazelcastInstance hazelcast;
@Autowired
private ProductVersioningService productVersioningService;
private IQueue<diagnosticRun> diagnosticRunQueue;
private ILock diagnosticRunLock;
private ExecutorService executorService;
@PostConstruct
public void init()
{
diagnosticRunQueue = hazelcast.getQueue(DIAGNOSTIC_RUN_QUEUE_NAME);
diagnosticRunLock = hazelcast.getLock("diagnosticRunLock");
executorService = Executors.newFixedThreadPool(1);
executorService.submit(() -> listenToDiagnosticRuns());
}
@PreDestroy
public void stop()
{
executorService.shutdown();
}
private void listenToDiagnosticRuns()
{
while(!executorService.isShutdown())
{
diagnosticRunLock.lock(5, TimeUnit.SECONDS);
try
{
DiagnosticRun diagnosticRun = diagnosticRunQueue.poll(1L, TimeUnit.SECONDS);
productVersioningService.updateProductDeviceTable(diagnosticRun);
}
catch(InterruptedException e)
{
logger.error("Interrupted polling diagnosticRun queue", e);
}
finally
{
diagnosticRunLock.unlock();
}
}
}
}
答案 0 :(得分:0)
首先,我将确定我不是完全执行线程的专家,何时执行这些线程可能会引起分歧,但这是我的想法,因此请任何人加入,因为这似乎是一个有趣的案例。您的第一个解决方案将Hazelcast事件线程与操作线程混合在一起。实际上,由于单个事件,您正在触发要调用的三个操作。如果在对updateProcductDeviceTable的调用中添加了任意延迟,则最终会看到它会变慢,但在一段时间后会重新恢复。这将导致您的本地事件队列在调用操作时堆积。您可以将您正在做的所有事情放在一个单独的线程中,您可以在#itemAdded上“唤醒”,或者如果可以承受一点延迟,则可以在第二种解决方案上进行操作。但是,我会在
中进行一些更改listenToDiagnosticsRuns()方法:
private void listenToDiagnosticRuns()
{
while(!executorService.isShutdown())
{
if(diagnosticRunQueue.peek() != null)
{
diagnosticRunLock.lock(5, TimeUnit.SECONDS);
try
{
DiagnosticRun diagnosticRun = diagnosticRunQueue.poll(1L, TimeUnit.SECONDS);
if(diagnosticRun != null)
{
productVersioningService.updateProductDeviceTable(diagnosticRun);
}
}
catch(InterruptedException e)
{
logger.error("Interrupted polling diagnosticRun queue", e);
}
finally
{
diagnosticRunLock.unlock();
}
} // peek != null
else
{
try
{
Thread.sleep(5000);
}
catch (InterruptedException e)
{
//do nothing
}
}
}
}