我最近刚使用ArrayBlockingQueue进行多线程处理。但它似乎放慢了速度而不是加速。你能帮助我吗?我基本上导入一个文件(大约300k行)并解析它们并将它们存储在DB中
public class CellPool {
private static class RejectedHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable arg0, ThreadPoolExecutor arg1) {
System.err.println(Thread.currentThread().getName() + " execution rejected: " + arg0);
}
}
private static class Task implements Runnable {
private JSONObject obj;
public Task(JSONObject obj) {
this.obj = obj;
}
@Override
public void run() {
try {
Thread.sleep(1);
runThis(obj);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void runThis(JSONObject obj) {
//where the rows are parsed and stored in the DB, etc
}
}
public static void executeCellPool(String filename) throws InterruptedException {
// fixed pool fixed queue
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(300000, true);
ThreadPoolExecutor executor = new ThreadPoolExecutor(90, 100, 1, TimeUnit.MINUTES, queue);
DataSet ds = CommonDelimitedParser.getDataSet(filename);
final String[] colNames = ds.getColumns();
while (ds.next()) {
JSONObject obj = new JSONObject();
//some JSON object
Task t = new Task(obj);
executor.execute(t);
}
}
}
答案 0 :(得分:4)
tl; dr大队列大小会产生负面影响,大线程数也会产生负面影响。理想情况下,您希望您的消费者和生产者以相似的速度工作。
添加队列导致问题的原因是因为您正在使用占用资源的非常大的队列(这不是必需的)。通常,当队列中没有剩余空间时,阻塞队列会阻塞生成器,而当队列中没有剩余对象时,阻塞队列会阻塞生成器。通过创建一个如此大的静态大小,当你几乎肯定没有使用它时,Java会在内存中分配这个空间。如果您的消费者消费者太慢,强制生产者等待队列中的空间来清理会更有效。您不需要同时将文件中的所有行存储在队列中。
线程池执行器队列在javadoc here中讨论。
有界队列。有限队列(例如,ArrayBlockingQueue)在与有限maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难以调整和控制。队列大小和最大池大小可以相互交换:使用大型队列和小型池最小化CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。如果任务经常阻塞(例如,如果它们是I / O绑定的),系统可能能够为您提供比您允许的更多线程的时间。使用小队列通常需要更大的池大小,这会使CPU更加繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。
您的大型线程大小为90,再加上非常大的池大小300000,很可能会占用大量内存,从而导致额外的线程调度开销。我会大大放弃他们两个。我不知道你在运行什么硬件,但是因为听起来你正在编写一个IO密集型程序,我会尝试加倍你的CPU可以处理的线程数量,并为你的大小调整阻止队列以查看哪些有效(注意:我还没有研究过这个,这是基于我运行队列和执行器的经验。很高兴其他人建议不同的计数!)。
但值得注意的是,如果您的队列太小,execute()
方法会因未能添加到队列而抛出RejectedExecutionException
。监视队列的一种方法是在安排任务之前检查其容量。您可以通过以下方式执行此操作:
executor.getQueue().remainingCapacity()
请勿使用executor.getQueue()
方法以任何方式更改队列,但可以将其用于监控。
另一种方法是使用无界限队列,例如没有定义容量的LinkedBlockingQueue
。这样,您就不需要处理队列大小。但是,如果您的生产者的运行速度比消费者快得多,那么您将再次遇到消耗过多内存的问题。
另外,kostya是对的,JDBC批量插入会更快。
答案 1 :(得分:2)
如果要尽可能快地将文件中的记录保存到关系数据库中,则应使用JDBC批量插入而不是逐个插入记录。