如何退出循环,包括 Callable ExecutiorService MultiThreading with Java (Android)
How to exit loop including Callable ExecutiorService MultiThreading with Java (Android)
我有一个用 SAX(dd-plist 库)解析的大型 plist (Xml) 文件。由于它是一个用于解析的大文件,并且出于性能原因,我必须使用多线程,我的目标是拥有与我的 plist 文件中键的确切数量相同的确切线程数,我的意思是对于 plist 中的每个键,单个线程搜索值并将其与 url 进行比较,如果键和 url 相等,则 return 键的值否则 return null 并跳过并取消线程(值是 html 内容的标题,键是存储在 plist 中的路径,而 url 是 link 的 url 用户点击和在 Android WebView 的 onPageFinished 中捕获)。如果有人告诉我上述目标,我将不胜感激,我错过了代码。
在我的 WebFragment (android.support.v4.app.Fragment 中,在 onPageFinished 中:
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.dd.plist.PropertyListParser;
...
try {
is = getResources().openRawResource(R.raw.title);
rootDict = (NSDictionary) PropertyListParser.parse(is);
dict = new LinkedHashMap<>();
dict = rootDict.getHashMap();
ExecutorService executor = Executors.newFixedThreadPool(rootDict.size());
Future<String> future;
String myStr = null;
String key;
NSObject value;
for (Map.Entry<String, NSObject> entry : dict.entrySet()) {
key = entry.getKey();
value = entry.getValue();
// following line is refer to WebFragment (line 285 where logs complain and crash because of the memory
future = executor.submit(new ParsePlistThread(key, value, url.substring(32).toString()));
myStr = future.get();
if (myStr != null && !myStr.isEmpty()) {
break;
} else {
//future.cancel(true);
}
}
executor.shutdown();
if (myStr != null) {
if (numTab == 0) {
titleTextView.setText(myStr);
}
} catch (Exception ex) {
//Handle exceptions...
}
这里是 ParsePlistThread class :
import com.dd.plist.NSObject;
import java.util.concurrent.Callable;
/**
* Created by manager on 2016-08-18.
*/
public class ParsePlistThread implements Callable<String> {
public String key;
public NSObject valueObject;
public String url;
public ParsePlistThread(String key , NSObject valueObj , String url) {
this.key = key;
this.valueObject = valueObj;
this.url = url;
}
@Override
public String call() throws Exception {
if (key.equals(url)) {
return valueObject.toString();
} else
{
return null;
}
}
}
这是日志:
E/art: Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again"
08-19 09:52:50.328 28749-28749/ca.ccohs.oshanswers E/AndroidRuntime: FATAL EXCEPTION: main
Process: XXX, PID: 28749
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:1063)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:920)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1327)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:103)
at ca.ccohs.oshanswers.ui.WebFragment.onPageFinished(WebFragment.java:285)
at com.android.webview.chromium.WebViewContentsClientAdapter.onPageFinished(WebViewContentsClientAdapter.java:531)
at org.chromium.android_webview.AwContentsClientCallbackHelper$MyHandler.handleMessage(AwContentsClientCallbackHelper.java:188)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6117)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
08-19 09:52:50.343 2850-29945/? E/android.os.Debug: ro.product_ship = true
08-19 09:52:50.343 2850-29945/? E/android.os.Debug: ro.debug_level = 0x4f4c
你的方法有问题
除了将极小的任务放入其他线程的概念问题(您将花费比实际计算更多的时间传递信息),此代码还有 2 个主要问题:
1) 你得到的实际错误。此错误是由于您 运行 堆栈内存不足造成的。 Java 内存分为多个区域,已知最大和最常见的区域是"Heap Memory"。这是(几乎)所有对象所在的地方。一个鲜为人知的区域是 "Stack Memory"。这是您的线程获取内存以存储当前状态、堆栈跟踪、本地(方法)变量等的地方。创建 Thread
时,它会从 [=72= 的堆栈中为其分配一些固定内存].如果创建的线程太多,它会 运行 out,并抛出一个错误,例如您遇到的错误。
解决方案 - 重用您的线程!
您的执行器具有内置功能,可以在完成任务后重用线程。更多内容请见下文。一般来说,CPU 中的线程多于逻辑内核将不会 提高速度。
2) 你实际上并没有同时做任何事情。在您的循环中,您正在向 Executor
(executor.submit
方法)提交任务,然后等待任务完成 (future.get
),然后转到下一行。因此,您正在等待当前任务完成,然后再创建新任务!您不会有 2 个任务 运行与此安排并行。
这里的最后一点是您不应该依赖多线程来加速文件处理。瓶颈几乎总是在读取文件。很可能是您对它做了一些愚蠢的事情,导致它变慢了。
多线程完成正确更好。
评论中提到,在值得的情况下,看看如何解决这些错误可能会很有用。下面是解决并发问题的半天真的方法。
第一件事是第一 - 限制你需要的线程。如果您 运行 在具有超线程的四核桌面上使用,我建议使用 6 个线程,或者只是一个工作窃取池。我不确定 Android 的行数是多少,但它肯定低于 "very large file" 中的行数。
ExecutorService executor = Executors.newFixedThreadPool(6);
或
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
这样,当您的任务多于可用线程时(因此 - 多于可用处理器,除非您的线程被 I/O 绑定),而不是创建更多线程,新任务被排队直到现有线程可用。
下一个问题其实就是把所有的任务都放到执行器中尽快执行,而不是顺序执行。为此,您必须跟踪您创建的期货(请注意,我还删除了每个循环中的子字符串,因为 URL 在调用之间似乎没有变化,因此您可以预先计算它。这是一个普遍的事情 - 不要在循环中重做你可以做一次的工作!)
List<Future<String>> tasks = new ArrayList<>();
for (Map.Entry<String, NSObject> entry : dict.entrySet()) {
key = entry.getKey();
value = entry.getValue();
tasks.add(executor.submit(new ParsePlistThread(key, value, url)));
}
既然你已经提交了所有的任务(我再次重申,如此大量地使用这么小的任务,一般情况下会适得其反),你需要收集结果。这样做很简单,只需遍历你的未来!
String result;
for (Future<String> fut : tasks) {
String taskResult = fut.get();
if (taskResult != null && !taskResult.isEmpty()) {
result = taskResult;
break;
}
}
您的方法与此方法有一个主要区别 - 如果找到结果,您的方法不会继续进行解析。 在此特定情况下,只需对您尚未访问的期货使用 future.cancel
即可实现。我会把代码留给你。一般来说,这比较困难,因为这将涉及线程间通信(您必须向另一个线程发出信号以优雅地停止其执行,这可能不是微不足道的)。
忠告 - 开始学习多线程,同时尝试提高速度并不是很有成效的恕我直言。它周围有很多微妙之处(上面甚至没有提到 2 个问题——语句重新排序和内存可见性),正确且快速地完成它们是一个相当大的挑战!尝试并行做某事要好得多,但不一定更快,但要正确。当您对正确地进行并行处理感到满意时,您可以考虑使它们更快。
我有一个用 SAX(dd-plist 库)解析的大型 plist (Xml) 文件。由于它是一个用于解析的大文件,并且出于性能原因,我必须使用多线程,我的目标是拥有与我的 plist 文件中键的确切数量相同的确切线程数,我的意思是对于 plist 中的每个键,单个线程搜索值并将其与 url 进行比较,如果键和 url 相等,则 return 键的值否则 return null 并跳过并取消线程(值是 html 内容的标题,键是存储在 plist 中的路径,而 url 是 link 的 url 用户点击和在 Android WebView 的 onPageFinished 中捕获)。如果有人告诉我上述目标,我将不胜感激,我错过了代码。
在我的 WebFragment (android.support.v4.app.Fragment 中,在 onPageFinished 中:
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.dd.plist.PropertyListParser;
...
try {
is = getResources().openRawResource(R.raw.title);
rootDict = (NSDictionary) PropertyListParser.parse(is);
dict = new LinkedHashMap<>();
dict = rootDict.getHashMap();
ExecutorService executor = Executors.newFixedThreadPool(rootDict.size());
Future<String> future;
String myStr = null;
String key;
NSObject value;
for (Map.Entry<String, NSObject> entry : dict.entrySet()) {
key = entry.getKey();
value = entry.getValue();
// following line is refer to WebFragment (line 285 where logs complain and crash because of the memory
future = executor.submit(new ParsePlistThread(key, value, url.substring(32).toString()));
myStr = future.get();
if (myStr != null && !myStr.isEmpty()) {
break;
} else {
//future.cancel(true);
}
}
executor.shutdown();
if (myStr != null) {
if (numTab == 0) {
titleTextView.setText(myStr);
}
} catch (Exception ex) {
//Handle exceptions...
}
这里是 ParsePlistThread class :
import com.dd.plist.NSObject;
import java.util.concurrent.Callable;
/**
* Created by manager on 2016-08-18.
*/
public class ParsePlistThread implements Callable<String> {
public String key;
public NSObject valueObject;
public String url;
public ParsePlistThread(String key , NSObject valueObj , String url) {
this.key = key;
this.valueObject = valueObj;
this.url = url;
}
@Override
public String call() throws Exception {
if (key.equals(url)) {
return valueObject.toString();
} else
{
return null;
}
}
}
这是日志:
E/art: Throwing OutOfMemoryError "pthread_create (1040KB stack) failed: Try again"
08-19 09:52:50.328 28749-28749/ca.ccohs.oshanswers E/AndroidRuntime: FATAL EXCEPTION: main
Process: XXX, PID: 28749
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:1063)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:920)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1327)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:103)
at ca.ccohs.oshanswers.ui.WebFragment.onPageFinished(WebFragment.java:285)
at com.android.webview.chromium.WebViewContentsClientAdapter.onPageFinished(WebViewContentsClientAdapter.java:531)
at org.chromium.android_webview.AwContentsClientCallbackHelper$MyHandler.handleMessage(AwContentsClientCallbackHelper.java:188)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6117)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
08-19 09:52:50.343 2850-29945/? E/android.os.Debug: ro.product_ship = true
08-19 09:52:50.343 2850-29945/? E/android.os.Debug: ro.debug_level = 0x4f4c
你的方法有问题
除了将极小的任务放入其他线程的概念问题(您将花费比实际计算更多的时间传递信息),此代码还有 2 个主要问题:
1) 你得到的实际错误。此错误是由于您 运行 堆栈内存不足造成的。 Java 内存分为多个区域,已知最大和最常见的区域是"Heap Memory"。这是(几乎)所有对象所在的地方。一个鲜为人知的区域是 "Stack Memory"。这是您的线程获取内存以存储当前状态、堆栈跟踪、本地(方法)变量等的地方。创建 Thread
时,它会从 [=72= 的堆栈中为其分配一些固定内存].如果创建的线程太多,它会 运行 out,并抛出一个错误,例如您遇到的错误。
解决方案 - 重用您的线程!
您的执行器具有内置功能,可以在完成任务后重用线程。更多内容请见下文。一般来说,CPU 中的线程多于逻辑内核将不会 提高速度。
2) 你实际上并没有同时做任何事情。在您的循环中,您正在向 Executor
(executor.submit
方法)提交任务,然后等待任务完成 (future.get
),然后转到下一行。因此,您正在等待当前任务完成,然后再创建新任务!您不会有 2 个任务 运行与此安排并行。
这里的最后一点是您不应该依赖多线程来加速文件处理。瓶颈几乎总是在读取文件。很可能是您对它做了一些愚蠢的事情,导致它变慢了。
多线程完成正确更好。
评论中提到,在值得的情况下,看看如何解决这些错误可能会很有用。下面是解决并发问题的半天真的方法。
第一件事是第一 - 限制你需要的线程。如果您 运行 在具有超线程的四核桌面上使用,我建议使用 6 个线程,或者只是一个工作窃取池。我不确定 Android 的行数是多少,但它肯定低于 "very large file" 中的行数。
ExecutorService executor = Executors.newFixedThreadPool(6);
或
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
这样,当您的任务多于可用线程时(因此 - 多于可用处理器,除非您的线程被 I/O 绑定),而不是创建更多线程,新任务被排队直到现有线程可用。
下一个问题其实就是把所有的任务都放到执行器中尽快执行,而不是顺序执行。为此,您必须跟踪您创建的期货(请注意,我还删除了每个循环中的子字符串,因为 URL 在调用之间似乎没有变化,因此您可以预先计算它。这是一个普遍的事情 - 不要在循环中重做你可以做一次的工作!)
List<Future<String>> tasks = new ArrayList<>();
for (Map.Entry<String, NSObject> entry : dict.entrySet()) {
key = entry.getKey();
value = entry.getValue();
tasks.add(executor.submit(new ParsePlistThread(key, value, url)));
}
既然你已经提交了所有的任务(我再次重申,如此大量地使用这么小的任务,一般情况下会适得其反),你需要收集结果。这样做很简单,只需遍历你的未来!
String result;
for (Future<String> fut : tasks) {
String taskResult = fut.get();
if (taskResult != null && !taskResult.isEmpty()) {
result = taskResult;
break;
}
}
您的方法与此方法有一个主要区别 - 如果找到结果,您的方法不会继续进行解析。 在此特定情况下,只需对您尚未访问的期货使用 future.cancel
即可实现。我会把代码留给你。一般来说,这比较困难,因为这将涉及线程间通信(您必须向另一个线程发出信号以优雅地停止其执行,这可能不是微不足道的)。
忠告 - 开始学习多线程,同时尝试提高速度并不是很有成效的恕我直言。它周围有很多微妙之处(上面甚至没有提到 2 个问题——语句重新排序和内存可见性),正确且快速地完成它们是一个相当大的挑战!尝试并行做某事要好得多,但不一定更快,但要正确。当您对正确地进行并行处理感到满意时,您可以考虑使它们更快。