我有一项任务,我需要从网上下载一个页面,用于多个实体(> 700)。代码的设计方式使得特定函数获取一个实体的名称,下载其资源页面,然后处理它以挖掘某些属性并将它们放入全局HashMap中。请参阅以下代码:
用于处理每个实体的全局数据结构:
static HashMap<String, HashMap<String, ArrayList<String> > > Table = new HashMap<String, HashMap<String, ArrayList<String>>>();
static ArrayList<String> allColumns = new ArrayList<String>();
单线程代码:
BufferedReader br = new BufferedReader(new FileReader(filePath_ListOfEntities));
String entityURL;
while ((entityURL = br.readLine()) != null) {
String entityID = entityURL.replace("http://dbpedia.org/resource/", "");
try{
GetRowForEntityURL(entityID); // Downloads page, processes it and updates the global DSs
}catch(Exception e)
{
System.out.println("Ignored: " + entityID + " Error: " + e.getMessage());
}
}
PrintTable(); // prints the global hashmap
通过下载每个实体的资源页面,处理变得快得多,这意味着速率确定操作是下载资源页面。请注意,实体的操作独立于其他实体,但页面处理只能在资源页可用性之后完成。因此,我尝试为函数GetRowForEntityURL(entityID)
创建一个单独的线程。以下是多线程代码,与单线程代码相比,它需要更多时间:
多线程代码:
BufferedReader br = new BufferedReader(new FileReader(filePath_ListOfEntities));
String entityURL;
ArrayList<Thread> threads = new ArrayList<>();
while ((entityURL = br.readLine()) != null) {
String entityID = entityURL.replace("http://dbpedia.org/resource/", "");
Thread t = new Thread(new Runnable() {
public void run()
{
try{
GetRowForEntityURL(entityID); // Downloads page, processes it and updates the global DSs
}catch(Exception e)
{
System.out.println("Ignored: " + entityID + " Error: " + e.getMessage());
}
}
});
t.run();
threads.add(t);
}
for(int i = 0; i < threads.size(); i++)
threads.get(i).join();
System.out.println("***********Threads Joined******************");
PrintTable(); // prints the global hashmap
为什么多线程代码不会更快,因为每个实体都应该并行处理,因此下载应该并行进行?这应该比单线程快得多。
修改
现在很明显,即使在使用T.start()
之后,下载也会在单个连接上进行。我需要改进下载代码以实际利用多个线程。这是我的下载代码,其中我尝试在每次调用中创建一个新连接(以及每个线程),但我想这没有用。
public static void downloadFile(String entityID) throws IOException {
String fileURL = "http://dbpedia.org/data/" + entityID + ".rdf";
String saveDir = inputFolder;
URL url = new URL(fileURL);
HttpURLConnection httpConn;// = (HttpURLConnection) url.openConnection();
int responseCode;// = httpConn.getResponseCode();
do{
httpConn = (HttpURLConnection) url.openConnection();
responseCode = httpConn.getResponseCode();
}
while(responseCode != HttpURLConnection.HTTP_OK);
// always check HTTP response code first
if (responseCode == HttpURLConnection.HTTP_OK) {
System.out.println("Downloading for: "+entityID);
String fileName = "";
String disposition = httpConn.getHeaderField("Content-Disposition");
if (disposition != null) {
// extracts file name from header field
int index = disposition.indexOf("filename=");
if (index > 0) {
fileName = disposition.substring(index + 10,
disposition.length() - 1);
}
} else {
// extracts file name from URL
fileName = fileURL.substring(fileURL.lastIndexOf("/") + 1,
fileURL.length());
}
// opens input stream from the HTTP connection
InputStream inputStream = httpConn.getInputStream();
String saveFilePath = saveDir + File.separator + fileName;
// opens an output stream to save into file
//saveFilePath.replace(".rdf", ".txt");
String downloadAt = inputFolder + entityID + ".txt";
FileOutputStream outputStream = new FileOutputStream(downloadAt);
int bytesRead = -1;
byte[] buffer = new byte[4096];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
//System.out.println("File downloaded");
} else {
System.out.println("Download Failed. Server replied HTTP code: " + responseCode);
}
httpConn.disconnect();
}
答案 0 :(得分:1)
使用线程池大小为1的Executor
来测试单线程速度。然后增加poolize以查看它如何影响时间。
然后注意当你有一个大小为500的池时,由于所有正在发生的上下文切换,性能实际上会减弱。
答案 1 :(得分:0)
这里出了点问题,我看不出它是什么,因为我们只有一小部分难题。
正如@EJP建议的那样,你可能只有一个连接[调制解调器?]到互联网。当线程1连接并等待响应时,线程2-n等待连接。因此,您实际上在做的是单线程。
如果你能以某种方式进行多路复用,那么也许你可以加快速度。例如浏览器执行新标签的方式: 在新标签页中打开 在新标签页中打开 等等 一切都会消失,但浏览器不会等待每个回复;它以异步方式处理回复。
如果目的地也没有多路复用,那将无效。
答案 2 :(得分:0)
正如其他答案所指出的那样,我试图通过在线程任务开始时打印一些消息来检查是否正在创建新线程,如下所示:
Thread t = new Thread(new Runnable() {
public void run()
{
System.out.println("New Thread Started");
try{
GetRowForEntityURL(entityID); // Downloads page, processes it and updates the global DSs
}catch(Exception e)
{
System.out.println("Ignored: " + entityID + " Error: " + e.getMessage());
}
}
});
它似乎是单线程的(不是一个具体说出结论的好方法,但我说明了这一点,因为它们是并行处理的,每个线程的相应消息应该有即时打印但发生的事情是用于打印的一条消息然后它需要花费很多时间,这必须是处理时间,然后是用于打印的滞后第二条消息之后)。所以,很明显该程序是单线程的。
我再次检查编写多线程代码然后,我意识到错误:我们需要调用T.run()
而不是调用T.start()
。这使得代码成为多线程,我可以通过瞬时打印消息来验证。但现在,正如@EJP和@edharned所说,服务器正在响应错误代码,但这是一个不同的问题。
在单独的并行线程中调用方法的正确代码:
ArrayList<Thread> threads = new ArrayList<>(); // Store the thread IDs so that you can join them back - basically it means that your main thread should wait for the parallel threads to complete the task they have been assigned to.
while ((entityURL = br.readLine()) != null) {
String entityID = entityURL.replace("http://dbpedia.org/resource/", "");
Thread t = new Thread(new Runnable() {
public void run()
{
try{
// Call any Function that you want to be executed in the parallel thread
GetRowForEntityURL(entityID); // Downloads page, processes it and updates the global DSs
}catch(Exception e)
{
System.out.println("Ignored: " + entityID + " Error: " + e.getMessage());
}
}
});
t.start(); // NOTE: This was the mistake. Call t.start() and not t.run()
threads.add(t); // add the thread ID in your record
}
//Join the threads i.e. wait till all the created threads have finished their task
for(int i = 0; i < threads.size(); i++)
threads.get(i).join();
T.run()
不会产生新线程但会在同一个线程中调用该函数,而T.start()
会产生一个新线程。有关Thread.start()
和Thread.run()
之间差异的更多信息,请参见this stackoverflow answer。