阻塞队列
1. 阻塞队列的定义
-
首先队列是一种特殊的线性表,它只能够在表的后端(rear)进行数据插入,在表的前端(front)进行数据获取;和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
-
支持阻塞的含义:
- 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
- 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空。
2. 阻塞队列的使用场景
- 阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
3. 阻塞队列的相关类及相关方法简介
-
BlockingQueue
-
该类是阻塞队列的父类,他是一个接口类,其余的阻塞队列都需要继承它。
-
BlockingQueue
中并非只有阻塞方法,也有非阻塞的方法:
- 抛出异常:当队列满时,如果再往队列里插入元素,会抛出
IllegalStateException("Queuefull")
异常。当队列空时,从队列里获取元素会抛出NoSuchElementException
异常。 - 返回特殊值:当往队列插入元素时,会返回元素是否插入成功,成功返回true。如果是移除方法,则是从队列里取出一个元素,如果没有则返回null。
- 一直阻塞:当阻塞队列满时,如果生产者线程往队列里
put()
元素,队列会一直阻塞生产者线程,直到队列可用或者响应中断退出。当队列空时,如果消费者线程从队列里take()
元素,队列会阻塞住消费者线程,直到队列不为空。 - 超时退出:当阻塞队列满时,如果生产者线程往队列里插入元素,队列会阻塞生产者线程一段时间,如果超过了指定的时间,生产者线程就会退出。
- 抛出异常:当队列满时,如果再往队列里插入元素,会抛出
-
-
常用的阻塞队列有以下几种,都是继承了
BlockingQueue
接口类的:ArrayBlockingQueue
- 一个由数组结构组成的有界阻塞队列。
- 该阻塞队列是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证线程公平的访问队列,所谓公平访问队列是指阻塞的线程,可以按照阻塞的先后顺序访问队列,即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的,当队列可用时,阻塞的线程都可以争夺访问队列的资格,有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置
LinkedBlockingQueue
- 一个由链表结构组成的有界阻塞队列。
- 该阻塞队列是一个用链表实现的有界阻塞队列。此队列的默认最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
PriorityBlockingQueue
- 一个支持优先级排序的无界阻塞队列。
- 该阻塞队列是一个支持优先级的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现
compareTo()
方法来指定元素排序规则,或者初始化PriorityBlockingQueue
时,指定构造参数Comparator
来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
DelayQueue
- 一个使用优先级队列实现的无界阻塞队列。
- 该阻塞队列是一个支持延时获取元素的无界阻塞队列。队列使用
PriorityQueue
来实现。队列中的元素必须实现Delayed
接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。 DelayQueue
可以用作缓存系统的设计:可以用DelayQueue
保存缓存元素的有效期,使用一个线程循环查询DelayQueue
,一旦能从DelayQueue
中获取元素时,表示缓存有效期到了。
SynchronousQueue
- 一个不存储元素的阻塞队列。
- 该阻塞队列是一个不存储元素的阻塞队列。每一个
put
操作必须等待一个take
操作,否则不能继续添加元素。SynchronousQueue
可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue
的吞吐量高于LinkedBlockingQueue
和ArrayBlockingQueue
。
LinkedTransferQueue
- 一个由链表结构组成的无界阻塞队列。
- 该阻塞队列主要多了
tryTransfer
和transfer
两个方法transfer()
- 如果当前有消费者正在等待接收元素(消费者使用
take()
方法或带时间限制的poll()
方法时),transfer()
方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer()
方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
- 如果当前有消费者正在等待接收元素(消费者使用
tryTransfer()
tryTransfer()
方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer()
方法的区别是tryTransfer()
方法无论消费者是否接收,方法立即返回,而transfer()
方法是必须等到消费者消费了才返回。
LinkedBlockingDeque
- 一个由链表结构组成的双向阻塞队列。
- 该阻塞队列是一个由链表结构组成的双向阻塞队列。所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。
- 该阻塞队列多了
addFirst()
、addLast()
、offerFirst()
、offerLast()
、peekFirst()
和peekLast()
等方法,以First单词结尾的方法,表示插入、获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。另外,插入方法add()
等同于addLast()
,移除方法remove()
等效于removeFirst()
。但是take()
方法却等同于takeFirst()
,不知道是不是JDK的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化LinkedBlockingDeque
时可以设置容量防止其过度膨胀。另外,双向阻塞队列可以运用在工作窃取模式中。
4. 阻塞队列的注意点
- 有界无界概念
- 有限队列就是长度有限,满了以后生产者会阻塞,无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞,当然空间限制来源于系统资源的限制,如果处理不及时,导致队列越来越大越来越大,超出一定的限制致使内存超限,操作系统或者JVM帮你解决烦恼,直接把你 OOM kill 省事了。
- 无界也会阻塞,为何?因为阻塞不仅仅体现在生产者放入元素时会阻塞,消费者拿取元素时,如果没有元素,同样也会阻塞。
- 有限队列就是长度有限,满了以后生产者会阻塞,无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞,当然空间限制来源于系统资源的限制,如果处理不及时,导致队列越来越大越来越大,超出一定的限制致使内存超限,操作系统或者JVM帮你解决烦恼,直接把你 OOM kill 省事了。
ArrayBlockingQueue
和LinkedBlockingQueue
实现的区别- 队列中锁的实现不同
ArrayBlockingQueue
实现的队列中的锁是没有分离的,即生产和消费用的是同一个锁;LinkedBlockingQueue
实现的队列中的锁是分离的,即生产用的是putLock,消费是takeLock
- 在生产或消费时操作不同
ArrayBlockingQueue
实现的队列中在生产和消费的时候,是直接将枚举对象插入或移除的;LinkedBlockingQueue
实现的队列中在生产和消费的时候,需要把枚举对象转换为Node<E>
进行插入或移除,会影响性能
- 队列大小初始化方式不同
ArrayBlockingQueue
实现的队列中必须指定队列的大小;LinkedBlockingQueue
实现的队列中可以不指定队列的大小,但是默认是Integer.MAX_VALUE
- 队列中锁的实现不同
5. 代码示例
/**
* 实现一个固定时间后获取数据输出的生产者、消费者模型
*/
/**
* 实际存放的数据类
*/
class Order {
private int price;
private String name;
public Order(int price, String name) {
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public String getName() {
return name;
}
}
/**
* 存放元素的封装
* 该封装是需要存放进 DelayQueue中的,而查看DelayQueue<E extends Delayed>
* 其中的元素是需要继承Delayed类的
*/
class ItemVo<T> implements Delayed {
private long responseTime; //延迟时间
private T data; //真实的存储数据
public ItemVo(long delayTime, T d) {
responseTime = delayTime*1000 + System.currentTimeMillis();
data = d;
}
public long getResponseTime() {
return responseTime;
}
public T getData() {
return data;
}
//获取当前剩余时间
@Override
public long getDelay(TimeUnit timeUnit) {
long remainingTime = timeUnit.convert(responseTime - System.currentTimeMillis(),
timeUnit);
return remainingTime;
}
//比较不同任务之间的剩余时间大小
@Override
public int compareTo(Delayed delayed) {
long t = this.getDelay(TimeUnit.MILLISECONDS) - delayed.getDelay(TimeUnit.MILLISECONDS);
if (t == 0) {
return 0;
} else if (t < 0) {
return -1;
} else {
return 1;
}
}
}
import java.util.concurrent.DelayQueue;
//生产者线程
class PutOrder implements Runnable {
public DelayQueue<ItemVo<Order>> queue;
public PutOrder(DelayQueue<ItemVo<Order>> q) {
queue = q;
}
/**
* 生产者线程负责将数放入到DelayQueue中,以供消费者去获取
*/
@Override
public void run() {
Order order3 = new Order(100, "中美关系");
ItemVo<Order> item3 = new ItemVo<>(10, order3);
queue.offer(item3);
Order order1 = new Order(55, "三体");
ItemVo<Order> item1 = new ItemVo<>(5, order1);
queue.offer(item1);
Order order2 = new Order(88, "1740");
ItemVo<Order> item2 = new ItemVo<>(8, order2);
queue.offer(item2);
System.out.println("数据存放完毕");
}
}
//消费者线程
class FetchOrder implements Runnable {
public DelayQueue<ItemVo<Order>> queue;
public FetchOrder(DelayQueue<ItemVo<Order>> q) {
queue = q;
}
/**
* 消费者线程从DelayQueue中获取元素(因为DelayQueue本身就是按优先级排序的阻塞队列,
* 所以获取的队列元素一定是延时最短的元素),DelayQueue本身是阻塞队列,当使用take()
* 这个阻塞方法能够获取元素时,表示条件成立,即获取时间到达,否则会一直阻塞,无法获取到元素
*/
@Override
public void run() {
System.out.println("enter FetchOrder run!");
while (true) {
try {
ItemVo<Order> itemVo = queue.take();
Order order = (Order)itemVo.getData();
System.out.println("Order name = " + order.getName() +
", price = " + order.getPrice() + ", this time = " + System.currentTimeMillis()
+ ", itemVo time = " + itemVo.getResponseTime());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Test {
//使用DelayQuene实现存储元素特定时间后被获取的功能
public static void main(String[] args) throws InterruptedException {
//首先创建一个queue
DelayQueue<ItemVo<Order>> queue = new DelayQueue<ItemVo<Order>>();
//构建出生产者和消费者线程
new Thread(new PutOrder(queue)).start();
new Thread(new FetchOrder(queue)).start();
//主线程做计时操作
for (int i = 0; i < 100; i++) {
Thread.sleep(500);
System.out.println(i*500);
}
}
}
线程池
1. 使用线程池的好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。 如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。它把T1,T3分别安排在服务器程序的启动和结束的时间段或者一些空闲的时间段,这样在服务器程序处理客户请求时,不会有T1,T3的开销了。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
2. 浅显的实现一个自定义线程池
-
线程池简单化来说,即事先就构建出一定数量的工作线程,当有任务的时候就提交任务给构建好的工作线程去执行,当任务数超过总的工作线程时则将任务暂时存放在任务队列中,等待有空闲的工作线程时,再去执行任务;根据上述方式,我们可以自定义一个线程池:
//自定义的线程池 class MyThreadPool { //最大线程数 public int thread_max; //任务队列 public BlockingQueue<Runnable> taskQueue; //工作线程存放数组 public WorkThread[] threads; //默认值 public static final int THREAD_MAX = 3; public static final int TASK_MAX = 5; public MyThreadPool() { this(3, 5); } public MyThreadPool(int thread_num, int task_num) { if (thread_num <= 0) { this.thread_max = THREAD_MAX; } else { this.thread_max = thread_num; } if (task_num <= 0) { task_num = TASK_MAX; } //构建任务队列,任务队列的创建要在工作线程开启之前,否则就会抛出异常 taskQueue = new ArrayBlockingQueue<Runnable>(task_num); //构建对应数量的工作线程 threads = new WorkThread[thread_max]; for (int i = 0; i < thread_num; i++) { threads[i] = new WorkThread(); threads[i].start(); } } public void execute(Runnable task) throws InterruptedException { taskQueue.put(task); } public void destroy() { System.out.println("now will stop all work Thread......"); for (int i = 0; i < this.thread_max; i++) { threads[i].stopWorkThread(); threads[i] = null; } taskQueue.clear(); } class WorkThread extends Thread { @Override public void run() { Runnable runnable = null; try { //终止工作线程的条件 while (!isInterrupted()) { if (taskQueue != null) { runnable = taskQueue.take(); if (runnable != null) { System.out.println(getId() + ", will start runable : " + runnable); runnable.run(); } } runnable = null; } } catch (Exception e) { } } //用来停止工作线程 public void stopWorkThread() { interrupt(); } } } //使用自定义的线程池完成工作 class UseMyThreadPool { // 任务类 static class MyTask implements Runnable { private String name; private Random r = new Random(); public MyTask(String name) { this.name = name; } // 执行任务 @Override public void run() { try { Thread.sleep(r.nextInt(100)+200); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getId()+" sleep InterruptedException:" +Thread.currentThread().isInterrupted()); } System.out.println("任务 " + name + " 完成"); } } public static void main(String[] args) throws InterruptedException { MyThreadPool threadPool = new MyThreadPool(); threadPool.execute(new MyTask("11111")); threadPool.execute(new MyTask("22222")); threadPool.execute(new MyTask("33333")); threadPool.execute(new MyTask("44444")); threadPool.execute(new MyTask("55555")); threadPool.execute(new MyTask("66666")); Thread.sleep(50000); threadPool.destroy(); } }
关于自定义的线程池,最核心的内容就是:
- 初始化一个工作线程数组,用来存放工作线程
- 构建一个阻塞队列,用来存放需要执行的任务
3. 线程池中的类关系
Executor
- 它是一个接口类,是
Executor
框架的基础,他将任务的提交和任务的执行分离开来 - 他其中只有一个方法:
void execute(Runnable var1);
- 它是一个接口类,是
ExecutorService
ExecutorService
接口继承了Executor
,并在这个基础上做了方法拓展,比如shutdown
、shutdownNow
、submit
方法等,该接口类可以说是真正的线程池的接口
AbstractExecutorService
AbstractExecutorService
抽象类实现了ExecutorService
接口中的大部分方法
ThreadPoolExecutor
ThreadPoolExecutor
是线程池的核心实现类,继承了AbstractExecutorService
,它是用来执行被提交的任务的
ScheduledExecutorService
ScheduledExecutorService
接口继承了ExecutorService
,提供了带周期执行功能的ExecutorService
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor
是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令
4. ThreadPoolExecutor
构造函数中各个参数的意义
-
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize
- 线程池中的核心线程数
- 如果执行了线程池的
prestartAllCoreThreads()
方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize
- 线程池中允许的最大线程数
keepAliveTime
- 线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。
- 默认情况下,该参数只在线程数大于
corePoolSize
时才有用,即假设核心线程数是5,最大线程数是10,那么当时间到后,只会杀掉超过核心线程数的5个线程
unit
keepAliveTime
的时间单位
workQueue
- 存放任务的阻塞队列
- 当线程池中的线程数超过它的
corePoolSize
的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue
,线程池实现了阻塞功能 - 一般来说,我们应该尽量使用有界队列,具体原因见下文线程池工作流程解析
threadFactory
- 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名
- 当然还可以更加自由的对线程做更多的设置,比如设置所有的线程为守护线程
Executors
静态工厂里默认的threadFactory
,线程的命名规则是pool-数字-thread-数字。
handler
- 线程池的饱和策略
- 当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
AbortPolicy
:直接抛出异常,默认策略CallerRunsPolicy
:用调用者所在的线程来执行任务DiscardOldestPolicy
:丢弃阻塞队列中靠最前的任务,并执行当前任务DiscardPolicy
:直接丢弃任务
- 当然也可以根据应用场景实现
RejectedExecutionHandler
接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务
5. ThreadPoolExecutor
的工作流程解析
-
线程池的流程:
- 如果当前运行的线程少于
corePoolSize
,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁) - 如果运行的线程等于或多于
corePoolSize
,则将任务加入BlockingQueue
- 如果无法将任务加入
BlockingQueue
(队列已满),则创建新的线程来处理任务(此时线程数小于maximumPoolSize
) - 如果创建新线程将使当前运行的线程超出
maximumPoolSize
,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()
方法
- 如果当前运行的线程少于
-
workQueue
为什么需要尽量使用有界队列- 当线程池中的线程数达到
corePoolSize
后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize
。 - 由于1,使用无界队列时
maximumPoolSize
将是一个无效参数。 - 由于1和2,使用无界队列时
keepAliveTime
将是一个无效参数。 - 更重要的,使用无界queue可能会耗尽系统资源,有界队列则有助于防止资源耗尽,同时即使使用有界队列,也要尽量控制队列的大小在一个合适的范围。
- 当线程池中的线程数达到
6. 线程池中任务的提交
-
使用线程池提交任务有两个方法
-
public void execute(Runnable command)
execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
-
public <T> Future<T> submit(Runnable task, T result)
public <T> Future<T> submit(Callable<T> task)
public <T> Future<T> submit(Callable<T> task)
submit()
方法用于提交需要返回值的任务- 线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功
- 我们还可以通过future对象的
get()
方法来获取返回值 - 需要注意的是:
get()
方法会阻塞当前线程直到任务完成;而使用get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
-
7. 关闭线程池
- 关闭线程池有两个方法
public void shutdown()
public List<Runnable> shutdownNow()
- 工作原理
- 它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止
- 两种方法的区别
shutdownNow()
方法会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表shutdown()
方法只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
- 判断线程池是否结束有两个方法
public boolean isShutdown()
- 只要调用了上面两个方法中的任何一个方法,
isShutdown()
方法就会返回TRUE
- 只要调用了上面两个方法中的任何一个方法,
public boolean isTerminated()
- 只有当所有的任务都已关闭后,才表示线程池关闭成功,此时调用
isTerminated()
方法才会返回TRUE
- 只有当所有的任务都已关闭后,才表示线程池关闭成功,此时调用
8. 合理的配置线程池
- 要想合理地配置线程池,就必须首先分析任务特性,可以从一下几个角度来分析
- 任务的性质:
- CPU密集型任务 : 纯计算任务
- IO密集型任务 :涉及到如网络连接操作、读取磁盘等频繁使用IO的任务
- 混合型任务
- 任务的优先级:高、中和低
- 任务的执行时间:长、中和短
- 任务的依赖性:是否依赖其他系统资源,如数据库连接
- 任务的性质:
- 根据任务特性来配置线程池的数量(该数量是最大线程数---->
maximumPoolSize
)- CPU密集型任务
- 应该配置尽可能少的线程,线程数 = 机器的CPU核心数 + 1
- 为什么 +1:避免出现页缺失,造成CPU资源浪费
- 即数据存储有可能一部分存储在内存中,一部分存放在虚拟内存(即分出一部分磁盘空间作为内存空间进行存放数据,该段空间就叫做虚拟内存)中,当某一个任务需要的数据是存放在虚拟内存中的时候,那么就需要CPU将虚拟内存中的数据先拷贝到内存中来再给当前任务使用,而从磁盘中读写数据速度是远远不如内存中读写的,那么当前任务线程就会由于等待数据的拷贝而被CPU暂时“挂起”,等到数据拷贝完成,再被"唤醒"继续执行,该种状态就是所谓的页缺失;而配置线程数+1,就是为了保证在多个线程运行时,有线程进入页缺失状态时,能够有另外的线程直接先开始运行,从而保证充分的利用CPU的资源。
- 为什么 +1:避免出现页缺失,造成CPU资源浪费
- 原因:此时CPU是保持一直运行的状态,如果在这种情况下开启很多线程,那么就徒增了上下文切换的时间,反而得不偿失
- 应该配置尽可能少的线程,线程数 = 机器的CPU核心数 + 1
- IO密集型任务
- 应配置尽可能多的线程, 线程数 = 机器的CPU核心数 * 2
- 原因:此时CPU并不是一直在执行任务,读取网络数据、读取磁盘的速度是有限的,在获取数据的过程中,相关任务线程就是处在“挂起”状态的,所以可以多做一些上下文切换的动作,从而充分利用CPU资源
- 混合型任务
- 混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解
- 可以通过
Runtime.getRuntime().availableProcessors()
方法获得当前设备的CPU个数
- CPU密集型任务
- 存在任务优先级的线程池配置
- 可以使用优先级队列
PriorityBlockingQueue
来处理。它可以让优先级高的任务先执行
- 可以使用优先级队列
- 任务执行时间不同的线程池配置
- 可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。
9. 代码示例
class UseThreadPool {
static class callWork implements Runnable {
public String name;
public callWork(String str) {
name = str;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "," + this.name);
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = new ThreadPoolExecutor(1, 4, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(2),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 20; i++) {
callWork callWorker = new callWork("callWork-" + i);
pool.execute(callWorker);
}
pool.shutdown();
}
}