.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list-item{list-style:none}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}
这一篇的内容主要来自于《java并发编程实战》,有一说一,看这种写的很专业的书不是很轻松,也没办法直接提高多少开发的能力,但是却能更加夯实基础,就像玩war3,熟练的基本功并不能让你快速地与对方拉开差距,但是却能再每一次团战中积累优势。
也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
但是,同步移交不适合管理资源的分配,除非特殊情况不推荐使用
我们最后可以这样理解:
使用无界队列可能会耗尽系统资源。 |
使用有界队列可能不能很好的满足性能,需要调节线程数和队列大小。 |
线程数也有开销,所以需要根据不同应用进行调节。 |
线程池的大小一直是大家很关心的问题,理想的大小取决于被提交任务的类型以及所部署的系统,代码中通常不会固定线程池的大小,而通过某种配置,或者Runtime.getRuntime().availableProcessors() 来动态计算。
Runtime.getRuntime().availableProcessors()这个代码大家可能不算太熟悉,这个方法可以获取CPU的数目。
综合起来最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 +1)* CPU数目。 至于+1的原因,则是当线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费(剩余价值压榨的满满的)。 当然,CPU并不是唯一影响线程池大小的资源,还应该考虑内存、文件句柄、套接字句柄、数据库连接等原因。
举个例子,比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为12,那么根据上面这个公式估算得到: ((0.5+1.5)/0.5+1)12=60
除了线程池大小上的显示设置以外,还可能由于其他资源上的约束而存在一些隐式限制,如应用程序使用一个包含10个连接的JDBC连接池,并且每个任务需要一个数据库连接,那么线程池就最好只有10个连接,因为当超过10个任务时,新的任务就需要其他任务释放连接。
线程池中,如果任务依赖于其他任务,那么就有可能产生死锁。在单线程的Executor中,如果一个任务将另一个任务提交到同一个Executor,并且等待这个被提交的任务的结果,那么就通常会产生死锁,这种情况被称为线程的饥饿死锁。
对于线程池来说,只要池任务开始了无限期阻塞,例如某个任务的目的是等待一些资源或条件,但是只有另一个池任务的执行才能使那些条件成立。除非能保证线程池足够大,否则会发生线程饥饿死锁。
下文清晰地展示了线程饥饿死锁的示例,队列为阻塞队列,因为线程池为是单线程的,当队列为空时,getHeader 将会一直阻塞等待 putHeader 执行。这就是任务之间相互依赖的饥饿死锁。
public class ThreadDeadlock {
//创建一个队列、假设是存放头文件的地方
private static BlockingQueue root = new ArrayBlockingQueue(10);
public static void main(String[] args) {
//创建一个固定线程的线程池
ExecutorService service = Executors.newSingleThreadExecutor();
service.submit(new getHeader());
service.submit(new putHeader(1));
service.shutdown();
}
static class putHeader implements Callable {
private int val;
public putHeader(int value) {
val = value;
}
@Override
public Object call() throws Exception {
System.out.println("放置头文件");
//往阻塞队列增加元素
root.put(1);
return "头文件";
}
}
static class getHeader implements Callable {
@Override
public Object call() throws Exception {
System.out.println("获取头文件");
//取出阻塞队列的值,如果没有则会阻塞
int value = (int) root.take();
return "头文件";
}
}
}
如果任务阻塞的时间过长,那么即使不出现死锁,线程池的响应性也会变得更糟。执行时间较长的任务不会造成线程池的堵塞,甚至还会增加执行时间较短任务的服务时间。如果线程池中的线程数量远小于在稳定状态下执行的任务的数量,那么到最后可能所有的线程都会运行这些执行时间较长的任务,从而影响整体的响应性。
可以通过限定任务等待资源的时间,不要去无限制地等待。在平台类库的大多数可阻塞方法中,都同时定义了限时版本和无限时版本,列如Thread.join、BlockingQueue.out、CountDownLatch.await以及Selector.select等。如果等待超时,那么可以把任务标识为失败,然后中止任务或者将任务重新放回队列。这样,无论任务的最终结果是否是成功,这种办法都能保证任务可以顺利执行而不会被阻塞住,并将线程释放出来执行一些能更快完成的任务。
当然了,如果线程池中总是充满了被阻塞的任务,也说明线程池设计的规模小了。
我是南橘,一名学习时长两年的程序员,希望这篇文章能帮助到大家。下面是我的微信二维码,有兴趣的朋友可以一起交流学习。