【Java并发】并发队列与线程池
并发队列
阻塞队列与非阻塞队
ConcurrentLinkedQueue
BlockingQueue
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
使用BlockingQueue模拟生产者与消费者
线程池
什么是线程池
线程池作用
线程池四种创建方式
newCachedThreadPool
newFixedThreadPool
newScheduledThreadPool
newSingleThreadExecutor
ThreadPoolExecutor
线程池原理剖析
自定义线程线程池
合理配置线程池
CPU密集
IO密集
如何合理的设置线程池大小。
并发队列
在并发队列上JDK
提供了两套实现,一个是以ConcurrentLinkedQueue
为代表的高性能队列 非阻塞队列,一个是以BlockingQueue
接口为代表的阻塞队列,无论哪种都继承自Queue
。
阻塞队列与非阻塞队
阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列.
1.ArrayDeque, (数组双端队列) 2.PriorityQueue, (优先级队列)
3.ConcurrentLinkedQueue, (基于链表的并发队列)
4.DelayQueue, (延期阻塞队列)(阻塞队列实现了BlockingQueue接口)
5.ArrayBlockingQueue, (基于数组的并发阻塞队列)
6.LinkedBlockingQueue, (基于链表的FIFO阻塞队列)
7.LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列)
8.PriorityBlockingQueue, (带优先级的无界阻塞队列)
9.SynchronousQueue (并发同步阻塞队列)
......
ConcurrentLinkedQueue
ConcurrentLinkedQueue
: 是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue
性能好于BlockingQueue
.它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null
元素。ConcurrentLinkedQueue
重要方法:add
和offer()
都是加入元素的方法(由于是无界的队列,在ConcurrentLinkedQueue
中这俩个方法没有区别)poll()
和peek()
都是取头元素节点,区别在于前者会删除元素,后者不会。
import java.util.concurrent.ConcurrentLinkedQueue;public class Q001_ConcurrentLinkedQueueTest {
public static void main(String[] args) {
ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>();
concurrentLinkedQueue.offer("aaa");
concurrentLinkedQueue.offer("bbb");
//从队列头获取元素,此方法没有将元素移出队列(peek--->偷看;看一眼;一瞥; )
System.out.println(concurrentLinkedQueue.peek());
System.out.println(concurrentLinkedQueue.size());
//从队列头获取元素,并将其移出队列
System.out.println(concurrentLinkedQueue.poll());
System.out.println(concurrentLinkedQueue.size());
//继续获取
System.out.println(concurrentLinkedQueue.poll());
//此时队列总没有元素了 输出为null
System.out.println(concurrentLinkedQueue.poll());
}
}
执行结果
aaa2
aaa
1
bbb
null
BlockingQueue
阻塞队列(
BlockingQueue
)是一个支持两个附加操作的队列。这两个附加的操作是:
- 在队列为空时,获取元素的线程会等待队列变为非空。
- 当队列满时,存储元素的线程会等待队列可用。
- 因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。
- 在
Java
中,BlockingQueue
的接口位于java.util.concurrent
包中(在Java5
版本开始提供),由上面介绍的阻塞队列的特性可知,阻塞队列是线程安全的。 - 在新增的
Concurrent
包中,BlockingQueue
很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。 - 阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
ArrayBlockingQueue
ArrayBlockingQueue
是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue
是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。add()和offer()方法的区别是如果队列满的话,add会抛出异常,offer返回false
下面是一个初始化和使用
ArrayBlockingQueue
的例子:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 阻塞队列
* 在队列满的时候,存储元素的线程会等待队列可用
* 队列为空时, 读取元素的线程 会等待队列变为非空
*
* @author hao
*
*/
public class Q002_ArrayBlockingQueue {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.offer("小明");
arrayBlockingQueue.offer("小李");
arrayBlockingQueue.offer("笑话", 3, TimeUnit.SECONDS);
System.out.println("插入第四条开始");
System.out.println(arrayBlockingQueue.offer("校长", 3, TimeUnit.SECONDS));
System.out.println("插入第四条结束");
//前三个元素一次出列
System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println("此时队列中元素"+arrayBlockingQueue.size());
//第四个元素出列(其实第四个元素没有入列)
System.out.println(arrayBlockingQueue.poll(3,TimeUnit.SECONDS));
System.out.println("此时队列中元素"+arrayBlockingQueue.size());
}
}
执行结果
插入第四条开始false # 这里延时了三秒
插入第四条结束
小明
小李
笑话
此时队列中元素0
null #这里延时了三秒
此时队列中元素0
LinkedBlockingQueue
LinkedBlockingQueue
阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE
的容量 。- 它的内部实现是一个链表。和
ArrayBlockingQueue
一样,LinkedBlockingQueue
也是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。 - 下面是一个初始化和使
LinkedBlockingQueue
的例子:
import java.util.concurrent.LinkedBlockingQueue;public class Q003_LinkedBlockingQueue {
public static void main(String[] args) {
LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<String>(2);
System.out.println(linkedBlockingQueue.offer("bbb"));
System.out.println(linkedBlockingQueue.offer("aaa"));
System.out.println(linkedBlockingQueue.offer("ccc")); //容量满的时候返回false
//add 和 offer 都可以添加队列 ,add会抛出异常 IllegalStateException
System.out.println(linkedBlockingQueue.add("ddd")); //容量满时抛出异常
}
}
执行结果
truetrue
false
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at com.hao.queue.Q003_LinkedBlockingQueue.main(Q003_LinkedBlockingQueue.java:12)
PriorityBlockingQueue
PriorityBlockingQueue
是一个没有边界的队列,它的排序规则和java.util.PriorityQueue
一样。需要注意,PriorityBlockingQueue
中允许插入null
对象。所有插入PriorityBlockingQueue
的对象必须实现java.lang.Comparable
接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的。另外,我们可以从PriorityBlockingQueue
获得一个迭代器Iterator,但这个迭代器并不保证按照优先级顺序进行迭代。
SynchronousQueue
SynchronousQueue
队列内部仅允许容纳一个元素。当一个线程插入一个元素后会被阻塞,除非这个元素被另一个线程消费。
使用BlockingQueue模拟生产者与消费者
生产者
import java.util.concurrent.BlockingQueue;import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ProducerThread implements Runnable {
private BlockingQueue<String> blockingQueue;
private AtomicInteger count = new AtomicInteger();
private volatile boolean FLAG = true;
public ProducerThread(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "生产者开始启动....");
while (FLAG) {
String data = count.incrementAndGet() + "";
try {
boolean offer = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
if (offer) {
System.out.println(Thread.currentThread().getName() + ",生产队列" + data + "成功..");
} else {
System.out.println(Thread.currentThread().getName() + ",生产队列" + data + "失败..");
}
Thread.sleep(1000);
} catch (Exception e) {
}
}
System.out.println(Thread.currentThread().getName() + ",生产者线程停止...");
}
public void stop() {
this.FLAG = false;
}
}
消费者
import java.util.concurrent.BlockingQueue;import java.util.concurrent.TimeUnit;
public class ConsumerThread implements Runnable {
private volatile boolean FLAG = true;
private BlockingQueue<String> blockingQueue;
public ConsumerThread(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "消费者开始启动....");
while (FLAG) {
try {
String data = blockingQueue.poll(2, TimeUnit.SECONDS);
if (data == null || data == "") {
FLAG = false;
System.out.println("消费者超过2秒时间未获取到消息.");
return;
}
System.out.println("消费者获取到队列信息成功,data:" + data);
} catch (Exception e) {}
}
}
}
测试代码
import com.hao.queuepc.ConsumerThread;import com.hao.queuepc.ProducerThread;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Test0005 {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>();
ProducerThread producer = new ProducerThread(blockingQueue);
ConsumerThread consumer = new ConsumerThread(blockingQueue);
Thread p = new Thread(producer);
Thread c = new Thread(consumer);
p.start();
c.start();
try {
//10秒后停止生产者线程
Thread.sleep(10*1000);
producer.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果
Thread-0生产者开始启动....Thread-1消费者开始启动....
Thread-0,生产队列1成功..
消费者获取到队列信息成功,data:1
Thread-0,生产队列2成功..
消费者获取到队列信息成功,data:2
Thread-0,生产队列3成功..
消费者获取到队列信息成功,data:3
Thread-0,生产队列4成功..
消费者获取到队列信息成功,data:4
Thread-0,生产队列5成功..
消费者获取到队列信息成功,data:5
Thread-0,生产队列6成功..
消费者获取到队列信息成功,data:6
Thread-0,生产队列7成功..
消费者获取到队列信息成功,data:7
Thread-0,生产队列8成功..
消费者获取到队列信息成功,data:8
Thread-0,生产队列9成功..
消费者获取到队列信息成功,data:9
Thread-0,生产队列10成功..
消费者获取到队列信息成功,data:10
Thread-0,生产者线程停止...
消费者超过2秒时间未获取到消息.
线程池
什么是线程池
Java
中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。
线程池作用
- 线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。
- 如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。
线程池四种创建方式
Java
通过Executors
(jdk1.5
并发包)提供四种线程池,分别为:
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO
,LIFO
, 优先级)执行。
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class P001_Cached {
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp = i;
newCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ",i" + temp);
}
});
}
}
}
执行结果
pool-1-thread-2,i1pool-1-thread-4,i3
pool-1-thread-3,i2
pool-1-thread-1,i0
pool-1-thread-5,i4
pool-1-thread-1,i5
pool-1-thread-4,i7
pool-1-thread-5,i6
pool-1-thread-3,i8
pool-1-thread-5,i9
总结: 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class P002_Fixed {
public static void main(String[] args) {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int temp = i;
newFixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "," + temp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
}
执行结果
pool-1-thread-1,0pool-1-thread-2,1
pool-1-thread-3,2
pool-1-thread-1,3
pool-1-thread-2,5
pool-1-thread-3,4
pool-1-thread-2,7
pool-1-thread-1,6
pool-1-thread-2,9
pool-1-thread-3,8
总结:因为线程池大小为3,每个任务输出index
后sleep
2秒,所以每两秒打印3个数字。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class P003_Scheduled {
public static void main(String[] args) {
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 10; i++) {
final int temp = i;
newScheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
}, 3, TimeUnit.SECONDS);
}
}
}
程序等待了3秒之后才输入如下内容:
执行结果
pool-1-thread-1,i:0pool-1-thread-2,i:1
pool-1-thread-3,i:2
pool-1-thread-2,i:4
pool-1-thread-1,i:3
pool-1-thread-2,i:6
pool-1-thread-3,i:5
pool-1-thread-2,i:8
pool-1-thread-1,i:7
pool-1-thread-3,i:9
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO
, LIFO
, 优先级)执行。示例代码如下:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class P004_Single {
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int temp = i;
newSingleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",i:" + temp);
}
});
}
}
}
//注意: 结果依次输出,相当于顺序执行各个任务。
执行结果
pool-1-thread-1,i:0pool-1-thread-1,i:1
pool-1-thread-1,i:2
pool-1-thread-1,i:3
pool-1-thread-1,i:4
pool-1-thread-1,i:5
pool-1-thread-1,i:6
pool-1-thread-1,i:7
pool-1-thread-1,i:8
pool-1-thread-1,i:9
ThreadPoolExecutor
Java
是天生就支持并发的语言,支持并发意味着多线程,线程的频繁创建在高并发及大数据量是非常消耗资源的,因为Java
提供了线程池。在jdk1.5
以前的版本中,线程池的使用是及其简陋的,但是在JDK1.5后,有了很大的改善。jdk1.5
之后加入了java.util.concurrent
包,java.util.concurrent
包的加入给予开发人员开发并发程序以及解决并发问题很大的帮助。
这里主要介绍下并发包下的Executor
接口,Executor
是 JDK1.5
时发布的Executor
框架的最顶层实现是ThreadPoolExecutor
类,Executors
工厂类中提供的newScheduledThreadPool
、newFixedThreadPool
、newCachedThreadPool
方法其实也只是ThreadPoolExecutor
的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,ThreadPoolExecutor
构造方中的几个参数如下:
corePoolSize
: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize
后,就会把到达的任务放到缓存队列当中maximumPoolSize
: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;keepAliveTime
: 表示线程没有任务执行时最多保持多久时间会终止。unit
: 参数keepAliveTime
的时间单位,有7种取值,在TimeUnit
类中有7种静态属性。workQueue
: 阻塞队列 BlockingQueue<Runnable>
,当核心线程池满的时候会放入阻塞队列中进行等待
线程池原理剖析
提交一个任务到线程池中,线程池的处理流程如下:
- 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
- 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
- 判断线程池里的线程(这里比较的是最大线程数)是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
自定义线程线程池
如果当前线程池中的线程数目小于corePoolSize
,则每来一个任务,就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize
,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;
若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果队列已经满了,则在总线程数不大于maximumPoolSize
的前提下,则创建新的线程如果当前线程池中的线程数目达到maximumPoolSize
,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize
时,如果某线程空闲时间超过keepAliveTime
,线程将被终止,直至线程池中的线程数目不大于corePoolSize
;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime
,线程也会被终止。
示例代码
import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class P005_ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
for (int i = 1; i <= 6; i++) {
TaskThred t1 = new TaskThred("任务" + i);
executor.execute(t1);
}
executor.shutdown();
}
}
class TaskThred implements Runnable {
private String taskName;
public TaskThred(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + taskName);
}
}
执行结果
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.hao.pool.TaskThred@33909752 rejected from java.util.concurrent.ThreadPoolExecutor@55f96302[Running, pool size = 2, active threads = 1, queued tasks = 0, completed tasks = 4] at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at com.hao.pool.P005_ThreadPoolExecutorTest.main(P005_ThreadPoolExecutorTest.java:13)
pool-1-thread-2任务5
pool-1-thread-2任务2
pool-1-thread-2任务3
pool-1-thread-2任务4
pool-1-thread-1任务1
合理配置线程池
CPU密集
CPU
密集的意思是该任务需要大量的运算,而没有阻塞,CPU
一直全速运行。
CPU
密集任务只有在真正的多核CPU
上才可能得到加速(通过多线程),而在单核CPU
上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU
总的运算能力就那些。
IO密集
IO
密集型,即该任务需要大量的IO
,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU
运算能力浪费在等待。所以在IO
密集型任务中使用多线程可以大大的加速程序运行,这种加速主要就是利用了被浪费掉的阻塞时间。
如何合理的设置线程池大小。
要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:
1. 任务的性质:CPU
密集型任务、IO
密集型任务、混合型任务。
2. 任务的优先级:高、中、低。
3. 任务的执行时间:长、中、短。
4. 任务的依赖性:是否依赖其他系统资源,如数据库连接等。
性质不同的任务可以交给不同规模的线程池执行。
对于不同性质的任务来说,CPU
密集型任务应配置尽可能小的线程,如配置CPU
个数+1的线程数;
IO
密集型任务应配置尽可能多的线程,因为IO
操作不占用CPU
,不要让CPU
闲下来,应加大线程数量,如配置两倍CPU
个数+1;
而对于混合型的任务,如果可以拆分,拆分成IO
密集型和CPU
密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了;
若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU
空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU
。
当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。
最佳线程数目 = ((线程等待时间+线程CPU
时间)/线程CPU
时间 )* CPU
数目
比如平均每个线程CPU
运行时间为0.5s
,而线程等待时间(非CPU运行时间,比如IO
)为1.5s
,CPU
核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32
。这个公式进一步转化为:
- 最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
可以得出一个结论:
- 线程等待时间所占比例越高,需要越多线程。
- **线程CPU时间所占比例越高,需要越少线程。 **
以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。
CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数
操作系统之名称解释:
某些进程花费了绝大多数时间在计算上,而其他则在等待
I/O
上花费了大多是时间,前者称为计算密集型(CPU密集型)
computer-bound
,后者称为I/O密集型,I/O-bound
。
以上是 【Java并发】并发队列与线程池 的全部内容, 来源链接: utcz.com/z/393617.html