Executor 饱和策略

前言

在使用线程池的时候,如果提交的任务超过了线程的大小,就会将任务放到一个队列中,这个队列可以使无界队列 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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值