使用线程池并发的时候有可能出现问题
线程饥饿死锁
如果第一个任务依赖于第二个任务的某个状态改变,而第二个任务要等待第一个任务执行完才能执行,就会造成饥饿死锁。
耗时阻塞
如果任务的耗时时间不同,最后任务可能会被一些耗时大的操作所占用,造成阻塞。
上面两个在足够大的线程池中都不会有问题,因此如何界定一个线程池的大小是很重要的。
通常决定一个池应该是多大的条件是:
稀缺资源,比如JDBC连接,如果JDBC连接为10个,那么同时的线程池就应该不超过10。
CPU个数,通常我们保证Ncpu + 1个线程的池来保证最优的利用率。
如果包含了阻塞或者等待的操作,那么可能不是所有的线程都处于计算中。比如,等待时间/计算时间=2,这个时候原来的1个CPU就可以同时给3个线程来使用,这个时候的基数应该为(1 + 等待时间/计算时间)
考虑上面多重因素后,保证CPU尽可能的出于运算状态,并且同时不会造成阻塞或者内存溢出等问题,达到最大的利用率就是池的最适合大小。
ThreadPoolExecutor中有核心池的大小,最大池的大小,和存活时间来共同管理着线程的创建和销毁。
核心池意味着大于他的超过存活时间的会被销毁掉, 最大池意味着再提交进来的会进入阻塞队列。不会创建新的线程。
比如newCachedThreadPool最大池这时为了Integer.MAX_VALUE,核心池设置为0,意味着可以无限增长,一旦不用一分钟后就会销毁。
管理队列
为了应付激增的请求,以及控制对CPU和内存的使用,可以使用队列。
排队的方式有三种:无限队列、有限队列和同步移交。
newFixedThreadPool使用的是一个无限LinkedBlockingQueue。
有限队列可以避免资源耗尽,但是会带来潜在的吞吐量约束。
对于庞大或者无限的池,可以使用SynchronousQueue做同步移交, 绕开队列,放在其中的任务必须有消费者,如果没有就会创建,如果饱和会执行饱和策略,比如被拒绝掉。
饱和策略
当有限队列充满后,饱和策略开始起作用。 ThreadPoolExecutor的饱和策略铜鼓setRejectedExecutionHandler来修改。
通常有这么集中方式: 终止(抛出异常)、遗弃(通常是遗弃最久的), 返回给调用者Executor.CallerRunsPolicy(),让调用者自己执行。
线程工厂
在需要的时候给线程池创建一个线程,比如处理UncaughtExceptionHandler的时候,这个时候我们可能需要使用日志记录一下,可以线程工厂中创建线程来完成这个操作。
扩展ThreadPoolExecutor
提供了几个钩子方法用来进行扩展beforeExecute, afterExecute, termiante.
并发递归:
public class TestCallable {
public static void main(String[] args) throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder build = factory.newDocumentBuilder();
Document doc = build.newDocument();
Element root = doc.createElement("root");
doc.appendChild(root);
for (int i = 0; i < 30; i++) {
Element e = doc.createElement("second" + i);
for (int j = 0; j < 100; j++) {
Element f = doc.createElement(e.getNodeName() + j);
e.appendChild(f);
}
root.appendChild(e);
}
long start = System.currentTimeMillis();
StringBuilder sb1 = new StringBuilder();
list1(sb1, doc.getDocumentElement());
long end = System.currentTimeMillis();
System.out.println(sb1.toString());
System.out.println(end - start);
long start2 = System.currentTimeMillis();
StringBuffer sb2 = new StringBuffer();
ExecutorService exec = Executors.newFixedThreadPool(50);
list2(exec, sb2, doc.getDocumentElement());
try {
exec.awaitTermination(0, TimeUnit.MILLISECONDS);
exec.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sb2.toString());
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);
}
private static void list2(ExecutorService exec,final StringBuffer sb2, Node element) {
if(element.hasChildNodes()){
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
final Node n = element.getChildNodes().item(i);
exec.submit(new Runnable() {
@Override
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb2.append(n.getNodeName());
}
});
list2(exec, sb2, n);
}
}
}
private static void list1(StringBuilder sb1, Node element) {
if(element.hasChildNodes()){
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
Node n = element.getChildNodes().item(i);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append(n.getNodeName());
list1(sb1, n);
}
}
}
}把线性的程序变为并发,如上可以大大的缩短执行时间。

被折叠的 条评论
为什么被折叠?



