线程池拒绝策略及代码演示

线程池:

为了避免重复的创建线程,线程池的出现可以让线程进行复用。当有任务提交的时候,就会向线程池拿一个线程,当任务完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。这样就可以避免频繁的创建线程而带来的不必要的性能开销.

使用线程池:

在java1.5版本以后,使用线程池建议使用java.util.concurrent包下的已经封装好了的三个静态方法:

  1. 创建指定线程大小的线程池
	Executor executor = Executors.newFixedThreadPool (int nThreads);
  1. 创建线程数量为1的线程池
 	Executor executor = Executors.newSingleThreadExecutor();
  1. 不指定大小,创建可以动态添加线程的线程池
 	Executor executor = Executors.newCachedThreadPool();

线程池的原理:

其实,上面三个方法实际上都是调用的ThreadPoolExecutor这个类的构造方法,只是参数不一致而已,下面我们看看ThreadPoolExecutor这个类的构造方法:

   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

用一个表格说明这个方法需要的参数的意义:

参数名 解释
int corePoolSize 核心池的大小
int maximumPoolSize 最大的线程池的大小
long keepAliveTime 线程池中超过 corePoolSize的空闲线程最大存活时间
TimeUnit unit keepAliveTime的时间单位,为一个枚举变量
BlockingQueue<Runnable> 阻塞任务的队列
ThreadFactory threadFactory 线程工厂
RejectedExecutionHandler handler 线程池的拒绝策略

线程池的工作流程

  1. 如果当前线程池的线程的数目小于corePoolSize,那么每提交一个任务,就会创建一个线程去执行这个任务;
  2. 如果线程池的数目大于corePoolSize,那么每提交一个线程,就会尝试将其提交到任务缓存队列,如果添加成功,任务会在线程空闲的时间去执行缓存队列里面的任务;如果添加失败(一般是创建了有界的缓存队列),那么就会另外去创建新的线程去执行这个任务.(这个情形下最大线程数maximumPoolSize 和缓存队列的长度之和要小于你总共需要创建的线程个数).
  3. 如果最大线程数maximumPoolSize 和缓存队列的长度之和大于你总共需要创建的线程个数,那么就会采取任务拒绝策略进行处理;
  4. 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止;
用代码演示下此工作流程
package RejectThread;

//需要执行的任务线程
public class SendNoticeTask implements Runnable {
    private int count;

    public void setCount(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println (Thread.currentThread ().getName () + "号工人    开始进行"  + count + "号工作任务");
        try {
            Thread.currentThread ().sleep (5000);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        System.out.println (Thread.currentThread ().getName ()+"号工人   执行完成");
    }
}
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形一
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> ());
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 3; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

在这里我们设置线程池的核心池大小为3,最大线程数量为10,任务缓存的队列没有设置大小,当你提交的线程数量小于或者等于3的时候,结果基本一样:
在这里插入图片描述如果我们重新设置下提交的任务数量,我们设置需要执行的线程数量为6,缓存队列不设置数量:

package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> ());
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 6; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

看下结果:
在这里插入图片描述
看的出来,前面三个任务0,1,2号工作任务是直接被执行了的,3,4,5这三个任务被放进了缓存队列,等到前面三个线程有空闲的时候才从缓存队列取出来执行.这个时候不论你提交多少个线程都是不进行拒绝策略的,因为一定可以提交到缓存队列.
下面看下添加失败的情形,我们设置LinkedBlockingQueue的队列大小为1,也就是只能放进去一个线程,在看下执行结果:

package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));//队列数量为1
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 6; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

看下执行的结果:
在这里插入图片描述看的出来,0,1,2,4,5号工作任务都是直接创建了线程并执行,而3号工作任务是存入了缓存队列,等待线程有空的时候才进行执行的.
其实,这个流程是,首先0,1,2这三个任务是直接创建线程执行的,因为corePoolSize的大小就是3,然后又来了任务3,4,5,没办法,往缓存队列里面添加,但是缓存队列的大小是1,所以只能加一个进去,也就是3号任务,4,5号任务就直接创建了新的线程来进行执行,等到线程有空闲的时候,从缓存队列里面拿出3号任务继续执行.

情形三在拒绝策略里面讲解.

线程池的拒绝策略

本文章主要演示的是线程池的拒绝策略,一共有4种:

拒绝策略handler 解释
AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
DiscardPolicy 丢弃任务,但是不抛出异常
DiscardOldestPolicy 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
CallerRunsPolicy 由调用线程处理该任务(最常见的是main线程)

在上述业务中,使用到拒绝策略的情形,我们设置corePoolSize还是3,maximumPoolSize 的大小是10,缓存队列大小还是1,这次我们执行15个任务,这个时候就会使用到拒绝策略了,代码如下:

  • 抛异常的拒绝策略AbortPolicy:
 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.AbortPolicy ());//抛异常的拒绝策略
        for (int i = 0; i < 15; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

结果如下:
在这里插入图片描述可以看出,0,1,2号任务先直接进行执行,3号任务进入缓存队列,4,5,6,7,8,9,10创建新的线程执行,此时已经达到最大线程创建数10,无法创建,执行拒绝策略,后面的11,12,13,14号任务直接被丢弃并且抛出异常.

  • 丢弃任务,不抛异常的策略DiscardPolicy
 public DiscardOldestPolicy() { }
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        //工作流程情形二
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardPolicy ());//不抛异常的拒绝策略
        for (int i = 0; i < 15; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

执行结果,比较简单,不作分析:
在这里插入图片描述

  • 由调用线程处理该任务(最常见的是main线程)的策略CallerRunsPolicy
    为了方便查看效果,我把执行任务的线程结束的输出改了下:
package RejectThread;

public class SendNoticeTask implements Runnable {
    private int count;

    public void setCount(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println (Thread.currentThread ().getName () + "号工人    开始进行"  + count + "号工作任务");
        try {
            Thread.currentThread ().sleep (5000);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        System.out.println (Thread.currentThread ().getName ()+"号工人    执行完成"  + count + "号工作任务~~~~~~");
    }
}

使用CallerRunsPolicy的策略:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }

注意这里:这里直接调用了需要执行线程的run(),相当于直接进行了方法的调用(大家可以理解下调用run()和start()启动线程的不同就可以理解区别),由于是在主线程里面,所以代表的是执行者运行的策略,CallerRun英文就是这个意思.

package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (1));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.CallerRunsPolicy ());
        for (int i = 0; i < 15; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
    }
}

查看执行结果:
在这里插入图片描述执行结果比较特殊,首先它不抛异常,其次主线程(main)加入了进来,图中上色的是首先弹出的结果,分析结果可以这么理解:这个策略是相当于main线程加入了线程池,也就是现在最大线程容量是11了,3号任务加入了我们定义的缓存队列,也就是当11号任务提交的时候,执行了拒绝策略,将任务分给主线程进行了,由于主线程执行任务也需要时间,所有提交任务的循环就先阻塞在这里了,等到主线程执行完才继续提交下一个任务,此时线程池中的线程已经有部分执行完成了,所以可以继续执行后面的任务.

  • 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)的策略DiscardOldestPolicy
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
package RejectThread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        System.out.println("主线程结开始");
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<> (2));
        poolExecutor.setRejectedExecutionHandler (new ThreadPoolExecutor.DiscardOldestPolicy ());
        for (int i = 0; i < 13; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            poolExecutor.execute(task);
        }
        System.out.println("主线程结束");

运行结果如下:
在这里插入图片描述分析结果可以看出,前面流程是一致的,0,1,2号任务直接创建,3,4号加入缓存队列,5~11新创建线程进行执行,然后你又提交了12号任务,系统的处理流程是你提交一个新的线程,先删除一个缓存队列里面的线程,调用
poolExecutor.execute()方法再执行一次,一直到所有的任务都是最新的,所以丢弃了缓存队列的第一个任务也就是3号任务,这样后续执行了4号和12号任务.

总结

线程池的拒绝策略就是上面四种,如果上面还是不能满足你的要求的话,就需要自定义拒绝策略,做法是写一个类,去实现RejectedExecutionHandler接口,自定义你的代码逻辑:

package RejectThread;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;

public class MyRejectPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        if (!executor.isShutdown()) {
            executor.getQueue().poll ();
            System.out.println("自定义的拒绝策略");
            executor.submit (r);
             //r.run ();
        }
    }
}

进行代码测试:

package RejectThread;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class RejectThreadTest {
    public static void main(String[] args) {
        System.out.println("主线程结开始");
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor (3, 10,
                0, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<> (2));
        poolExecutor.setRejectedExecutionHandler (new MyRejectPolicy());
        for (int i = 0; i < 14; i++) {
            SendNoticeTask task = new SendNoticeTask();
            task.setCount(i);
            executor.submit (r);  
        }
        System.out.println("主线程结束");

输出结果:
在这里插入图片描述
前面分析应该很简单,主要是当提交第12号任务的时候,它首先打印了我们输出的话,然后使用线程池进行任务的执行,这个时候它就需要等待了.

发布了39 篇原创文章 · 获赞 1 · 访问量 4620

猜你喜欢

转载自blog.csdn.net/thetimelyrain/article/details/97523175