前言
在使用线程池的时候,如果提交的任务超过了线程的大小,就会将任务放到一个队列中,这个队列可以使无界队列 LinkedBlockingQueue,也可是是有界队列 ArrayBlockingQueue。
当有界队列满了的时候,饱和策略就开始发挥作用。
ThreadPoolExecutor 的饱和策略可以通过调用 setRejected
ExecutionHandler 来修改。JDK 提供了几种不同的 Rejected
ExecutionHandler 实现,每一种实现都包含不同的饱和策略:
- AbortPolicy
- CallerRunsPolicy
- DiscardPolicy
- DiscardOldestPolicy
AbortPolicy
终止策略是默认的饱和策略,该策略将抛出未检查的 RejectedExecutionException。调用者捕获该异常,根据需求编写处理代码。
DiscardPolicy
如果新提交的任务无法保存到队列中时,抛弃策略会悄悄抛弃该任务。
DiscardOldestPolicy
该策略会抛弃下一个将要被执行的任务,然后尝试重新提交任务。
如果队列使用的是优先级队列,那么久会将优先级最高的任务抛弃。
**所以,最好不要将 DiscardOldestPolicy 策略和优先级队列一起使用。 **
CallerRunsPolicy
调用者运行策略实现了一种调节机制,该策略不会丢弃任务,也不抛出异常,而是将任务回退给调用者,降低新任务的流量。它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了 execute 方法的线程中执行该任务。
public class PolicyTest {
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(100));
static {
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
}
public static void main(String[] args) {
ServerSocket server = null;
Socket socket = null;
try {
server = new ServerSocket(8080);
while (true) {
socket = server.accept();
executor.execute(new SockerHandler(socket));
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static class SockerHandler implements Runnable {
private Socket socket;
public SockerHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
System.out.println(socket.getInputStream().toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如上代码,实现了一个 socket 的服务端。设置基础线程5个,最大线程10个,过期时间1分钟,使用有界队列 100,使用的是调用者运行策略。
当线程池中的线程都使用在运行时,并且有界队列已满的时候,下一个任务就会在调用 execute 的主线程中执行。
由于执行任务需要一定的时间,主线程在一定时间内就不能提交任务,使工作线程有一定的时间来执行任务。
在这期间,主线程不会调用 accept 方法,因此可以将请求保存在 TCP 层的队列中,而不是应用层的队列中。
如果持续过载,TCP层最终发现它的队列请求已满,开始抛弃请求。
当服务器过载时,这种过载情况就会慢慢向外蔓延,从线程池到工作队列到应用程序,再到TCP层,最终到客户端。
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(100));
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
使用信号量 Semaphore 做一个限流器:
/**
* 使用信号量做限流器
*
* @author apple
*/
@ThreadSafe
public class BoundedExecutor {
private final Executor exec;
private final Semaphore semaphore;
public BoundedExecutor(Executor exec, int bound) {
this.exec = exec;
this.semaphore = new Semaphore(bound);
}
public void submitTask(final Runnable command) throws InterruptedException {
semaphore.acquire();
try {
exec.execute(() -> {
try {
command.run();
} finally {
semaphore.release();
}
});
} catch (Exception e) {
semaphore.release();
}
}
}