文章目录
一、知识点
1.什么是线程池?
2.为什么要使用线程池?
3.线程池有什么作用?
4.说说几种常见的线程池及使用场景
5.线程池中的几种重要的参数
6.java线程池的核心线程数与最大的线程数的区别?
7.说说线程池的拒绝策略
第一种说法:
第二种说法:
线程池的拒绝策略用于处理当线程池无法接受新任务时的情况。当线程池中的工作线程已经达到最大线程数,并且任务队列也已满时,就会触发拒绝策略来处理新提交的任务。
Java 中的 ThreadPoolExecutor 类提供了四种内置的拒绝策略:
- AbortPolicy(默认):该策略直接抛出 RejectedExecutionException 异常,阻止任务的提交。
- CallerRunsPolicy:该策略将任务回退给调用者,即在提交任务的线程中执行该任务。这样可以降低新任务的流量,但是可能会影响调用线程的性能。
- DiscardOldestPolicy:该策略丢弃任务队列中最旧的任务,然后尝试重新提交新的任务。通过使用这种策略,可以保证正在执行的任务能够被优先处理。
- DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是一种比较好的选择。
除了上述内置的拒绝策略外,你还可以自定义拒绝策略,只需实现 RejectedExecutionHandler 接口并重写 rejectedExecution() 方法。例如:
import java.util.concurrent.*;
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义处理逻辑
System.out.println("Task rejected: " + r.toString());
// 可以根据实际需求进行拒绝策略的具体实现
}
}
public class ThreadPoolExample {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
RejectedExecutionHandler handler = new CustomRejectedExecutionHandler();
ExecutorService executor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
handler);
// ...
}
}
在上述示例中,我们创建了一个自定义的拒绝策略类 CustomRejectedExecutionHandler,并将其传递给线程池的构造函数。在 rejectedExecution() 方法中,你可以根据实际需求来处理被拒绝的任务。
选择合适的拒绝策略取决于应用程序的需求和特定场景。希望这些解释能够帮助到你!如果还有其他问题,请随时提问。
8.线程池的关闭
9.(了解)线程池都有哪几种工作队列
第一种说法:
第二种说法:
线程池中的工作队列决定了任务的处理顺序和方式。Java 中的 ThreadPoolExecutor 类提供了三种内置的工作队列,它们分别是:
- ArrayBlockingQueue:这是一个基于数组实现的有界阻塞队列,可以存储固定数量的任务。当任务提交时,如果线程池中的线程数还没有达到核心线程数,则会创建新的线程来执行任务。如果线程池中的线程数已经达到核心线程数,并且队列已满,则新提交的任务将会被阻塞,直到有空闲线程或者队列空出位置。
- LinkedBlockingQueue:这是一个基于链表实现的无界阻塞队列,可以存储任意数量的任务。当任务提交时,如果线程池中的线程数还没有达到核心线程数,则会创建新的线程来执行任务。如果线程池中的线程数已经达到核心线程数,并且队列已满,则新提交的任务将会被放在队列末尾等待处理。
- SynchronousQueue:这是一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,反之亦然。也就是说,该队列会立即将任务交给线程执行,如果没有空闲线程,则会直接创建新线程执行任务,因此最大线程数的设置将被忽略。
除了上述内置的工作队列外,还可以自定义工作队列,只需实现 BlockingQueue 接口并重写其中的方法。例如:
import java.util.concurrent.*;
public class CustomBlockingQueue implements BlockingQueue<Runnable> {
// 自定义实现逻辑,可以使用链表、数组等数据结构
}
public class ThreadPoolExample {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new CustomBlockingQueue();
ExecutorService executor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue);
// ...
}
}
在上述示例中,我们创建了一个自定义的工作队列类 CustomBlockingQueue,并将其传递给线程池的构造函数。在 CustomBlockingQueue 类中,你可以根据实际需求来实现具体的队列逻辑。
选择合适的工作队列取决于应用程序的需求和特定场景。希望这些解释能够帮助到你!如果还有其他问题,请随时提问。
10.线程池创建
<!--引入commons-lang3,为了创建阿里线程池-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
//这里使用的是ThreadPoolExecutor的完整版构造函数
private static final ThreadPoolExecutor singlePool = new ThreadPoolExecutor(10,10,100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build()
,new ThreadPoolExecutor.CallerRunsPolicy());
//下面的代码就是直接执行异步调用的代码
@Test
public void testThread(){
singlePool.execute(new Runnable() {
@Override
public void run() {
System.out.println("index:1");
System.out.println(Thread.currentThread().getName() + ": run");
}
});
}
二、问题
问题1:ExecutorService和ThreadPoolExecutor联系?
ExecutorService
ThreadPoolExecutor
问题2:多线程批处理代码
注意:多线程批处理方案,需要用到阻塞队列+线程池实现,阻塞队列是需要它的延迟效果,而之所以不用线程的sleep或者锁+唤醒方案,是因为太费事且复杂。
要求:假设我有个map里面有100个值,我想实现批处理20个线程每隔5秒延期处理一次。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Test
void parseString13() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 100; i++) {
map.put(i, "" + i);
}
final DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();
int i = 0;
long delay = 5000;
// 根据指令集合,按照核心线程数分批次执行,每批次间隔时间暂时设置为5s
for (Map.Entry<Integer, String> entry : map.entrySet()) {
Integer neId = entry.getKey();
String cmd = entry.getValue();
if (i != 0 && i % 20 == 0) {
delay = delay + 5000;
}
DelayedElement element = new DelayedElement(delay, cmd, neId);
delayQueue.offer(element);
i++;
}
ExecutorService executorService = Executors.newFixedThreadPool(20);
try {
if (delayQueue.size() > 0) {
do {
DelayedElement element = delayQueue.take();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",current time:" + new Date() + ",element:" + element);
}
});
} while (delayQueue.size() != 0);
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("InterruptedException:" + e.getMessage());
}
}
pool-1-thread-1,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='0', now=1701937289272, neId='0'}
pool-1-thread-2,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='1', now=1701937289272, neId='1'}
pool-1-thread-4,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='7', now=1701937289272, neId='7'}
pool-1-thread-3,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='3', now=1701937289272, neId='3'}
pool-1-thread-5,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='8', now=1701937289272, neId='8'}
pool-1-thread-7,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='2', now=1701937289272, neId='2'}
pool-1-thread-8,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='5', now=1701937289272, neId='5'}
pool-1-thread-6,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='4', now=1701937289272, neId='4'}
pool-1-thread-9,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294272, msg='6', now=1701937289272, neId='6'}
pool-1-thread-10,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='15', now=1701937289279, neId='15'}
pool-1-thread-11,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='16', now=1701937289279, neId='16'}
pool-1-thread-12,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='17', now=1701937289279, neId='17'}
pool-1-thread-13,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='18', now=1701937289279, neId='18'}
pool-1-thread-14,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='9', now=1701937289279, neId='9'}
pool-1-thread-15,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='19', now=1701937289279, neId='19'}
pool-1-thread-16,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='10', now=1701937289279, neId='10'}
pool-1-thread-17,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='11', now=1701937289279, neId='11'}
pool-1-thread-18,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='12', now=1701937289279, neId='12'}
pool-1-thread-20,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='14', now=1701937289279, neId='14'}
pool-1-thread-19,current time:Thu Dec 07 16:21:34 CST 2023,element:DelayedElement{delay=5000, expire=1701937294279, msg='13', now=1701937289279, neId='13'}
pool-1-thread-1,current time:Thu Dec 07 16:21:39 CST 2023,element:DelayedElement{delay=10000, expire=1701937299279, msg='31', now=1701937289279, neId='31'}
......
问题3:创建线程有2种方式,我用哪种?
方式1:
ThreadPoolExecutor singlePool = new ThreadPoolExecutor(10,10,100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100),
new BasicThreadFactory.Builder().namingPattern(“schedule-pool-%d”).daemon(true).build()
,new ThreadPoolExecutor.CallerRunsPolicy());
方式2:ExecutorService executorService = Executors.newFixedThreadPool(20);
结论:
如果简单的创建线程池就用“方式2”,只需指定线程数即可;
如果想对线程池的全面控制和配置请使用“方式1”。
问题4:方法【execute(Runnable)、submit(Runnable)、…】使用说明
注意:有这么多方法,我究竟用哪个?
下面是委托任务给ExecutorService的一些不同的方式:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(…)
- invokeAll(…)
下面来逐个看看这些方法。
execute(Runnable)
execute(Runnable) 方法接受一个java.lang.Runable对象的实例,并异步执行之。下面是一个使用ExecutorService执行Runnable的例子:
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
这种方式不能获得Runnable执行的结果,如果有这种需要,你将要使用Callable。
submit(Runnable)
submit(Runnable) 方法也接收一个Runnable接口的具体实现,并返回一个Future对象。Future对象可以用来检测Runable是否执行完成。
Future future = executorService.submit(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
future.get(); //returns null if the task has finished correctly.
submit(Callable)
submit(Callable)方法与submit(Runnable)方法相似,除了接收的参数有所不同。Callable实例非常类似于Runnable,不同的是call方法可以返回一个结果,Runnable.run()方法不能返回一个结果(因为是void类型),就算线程执行完了,成功了future.get()也只是得到null
可以通过submit(Callable)方法返回的Future对象获取Callable的结果。下面是一个使用Callable的例子:
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
System.out.println("Asynchronous Callable");
return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());
上面代码的输出结果是:
Asynchronous Callable
future.get() = Callable Result
invokeAny(…)
invokeAny()方法接收一个Callable对象或者Callable的子接口实例的集合作为参数,这个方法不会返回Future,但会返回集合中某一个Callable的结果。你不能确定你得到是哪个Callable的结果。只是已执行完成的Callable中的一个。
如果一个任务已经完成(或者抛出了异常),剩余的Callable任务将被取消。
下面是示例代码:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();
示例代码将会打印给定的Callable集合中一个Callable任务返回的结果。我尝试执行了多次,结果是变化的。有时候是“Task1”,有时候是“Task 2”等。
invokeAll(…)
invokeAll()接收一个Callable对象的集合作为参数,该方法会调用你传给他的集合中的所有Callable对象。Invoke()会返回一个Future对象的列表,通过这个列表你可以获取每一个Callable执行的结果。
一个任务可能会因为一个异常而结束,因此这时任务并不是真正意义上执行成功了。这在Future上是没有办法来判断的。
处理一个任务的容器(collection),并返回一个Future的容器。两个容器具有相同的结构,这里提交的任务容器列表和返回的Future列表存在顺序对应的关系。
下面是示例代码:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();