Java线程池使用场景和方法分析
原创
©著作权归作者所有:来自51CTO博客作者demo123567的原创作品,请联系作者获取转载授权,否则将追究法律责任
线程池的研究
前期准备程序
Executor executor = ExecutorUtils.getExecutor(2);
创建新的线程,直接在最外层Executor
new Thread(new Runnable() {
@Override
public void run() {
while (true){
executor.execute(()->{
logger.info(Thread.currentThread().getName());
});
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
效果是每秒钟线程池内的线程交替打印。
创建新的线程,在里面sleep
new Thread(new Runnable() {
@Override
public void run() {
while (true){
executor.execute(()->{
logger.info(Thread.currentThread().getName());
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}).start();
代码直接出现异常。
直接在外部使用执行方法
executor.execute(()->{
logger.info(Thread.currentThread().getName());
});
只打印了一次。
直接在外部循环打印
executor.execute(()->{
while (true){
logger.info(Thread.currentThread().getName());
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
只用到了一个线程。从这个实验我们可以得出一个结论,线程池的一堆线程是并发的时候使用的,如果没有形成并发,就不会使用新的线程。 所以我进行了接下来的尝试。
同时直接在外面调用两个外部循环打印
executor.execute(()->{
while (true){
logger.info(Thread.currentThread().getName());
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executor.execute(()->{
while (true){
logger.info(Thread.currentThread().getName());
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
可以发现这就是并发的了。那么如果我想只在一个execute调用里面实现这样的功能该怎么做呢?
总结
Executor.execute仅仅只是执行代码,如果想要并发执行,只能进行多次调用。即,下面为完整的例子:
PriorityBlockingQueue<Integer> task = new PriorityBlockingQueue<>();
Executor executor = ExecutorUtils.getExecutor(2);
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(2*100);
} catch (InterruptedException e) {
e.printStackTrace();
}
task.add(new Double(Math.random()*10000).intValue());
}
}
}).start();
for (int i=0;i<2;i++){
executor.execute(() -> {
while (true) {
Integer value = null;
try {
value = task.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (Objects.nonNull(value)){
logger.info(value);
try {
Thread.sleep(MathUtils.getPositiveRandomInt(3)*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
我用一个线程每隔200毫秒向队列里面丢一个数字,然后用线程池去取。然后executor.execute通过循环调用两次。就可以实现抢占式线程池调用了。
得想想上面的代码如何工具化。
批量执行任务集
ExecutorService service = ExecutorUtils.getExecutorService(2);
List<Callable<Integer>> listTask = new ArrayList<>();
for (int i = 0; i < 100; i++) {
listTask.add(() -> {
int l = MathUtils.getPositiveRandomInt(100);
int r = MathUtils.getPositiveRandomInt(100);
Integer value = MathUtils.getRandomInt(Math.min(l, r), Math.max(l, r));
logger.info(value);
Thread.sleep(value);
return value;
});
}
try {
List<Future<Integer>> futures = service.invokeAll(listTask);
logger.info("------------------------------------");
for (int i=0;i<100;i++){
try {
logger.info(futures.get(i).get());
} catch (Exception e) {
e.printStackTrace();
}
}
service.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这种方式适合于批量执行任务,但是事先需要确定出总的任务量,等到执行完了再出结果。
通过
List<Future<Integer>> futures = service.invokeAll(listTask);
得到返回值,可以做一些合并操作。该方法适用于放在逐层局部需要进行并发执行的过程,例如爬虫,爬取一个网站的时候可以考虑逐层爬取,就可以使用该方法来做。
总结
当我们需要使用线程池的时候,如果不确定需要处理的对象有多少,就采用队列的方式,利用阻塞队列和抢占式的方法来实现线程池。如果对于每一次并发任务,知道