一、要自定义线程池,需要使用ThreadPoolExecutor类。
ThreadPoolExecutor类的构造方法:
public ThreadPoolExecutor(int coreSize,int maxSize,long KeepAliveTime,TimeUnit unit,BlockingQueue queue,ThreadFactory factory,RejectedExectionHandler handler)
上述构造方法共有七个参数,这七个参数的含义分别是:
①int coreSize : 核心线程数(不会被回收)。
②int maxSize : 最大线程数。
③long KeepAliveTime : 膨胀出来的线程回收时间。
④TimeUnit unit : 时间单位。(通过TimeUnit.时间单位调用)
⑤BlockingQueue queue : 线程的阻塞队列。(必须有界)
⑥ThreadFactory factory : 产生线程的工厂。
⑦RejectedExecutionHandler handler : 当线程大于总数(最大线程数 + 阻塞队列)时,将由handler拒绝任务。
二、BlockingQueue接口
BlockingQueue接口是一个队列,下有实现类LinkedBlockingQueue,LinkedBlockingQueue是一个基于链表的有界或无界的队列,指定参数既有界,否则为无界。在自定义线程池时,一定要用界队列用来限制线程的等待数。
三、ThreadFactory接口
ThreadFactory依然是一个接口,用来表示产生线程的工厂。可以直接调用Executors中的静态方法defaultThreadFactory()来实现,也可以重写ThreadFactory中的newThread()方法。在这里我们使用重写ThreadFactory中的newThread()方法。
四、RejectedExecutionHandler接口
RejectedExecutionHandler接口的作用是当线程池达到满负荷的时候,就会调用该接口中的rejetedExecution(Runnable r, ThreadPoolExecutor pool)方法来拒绝新来的线程任务。
其中:
Runnable r:表示该方法拒绝了哪个任务。
ThreadPoolExecutor pool:表示哪个线程池拒绝了任务。
五,代码案例
1、创建RunnableImpl类实现 Runnable 接口
public class RunnableImpl implements Runnable {
@Override
public void run() {
// 获取当前线程的名字,并打印
String name = Thread.currentThread().getName();
System.out.println(name + "正在运行!");
try {
// 为了更好的观察线程运行的情况,让每个线程睡眠3秒
Thread.sleep(3000);
System.out.println("睡着了···");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
String name = Thread.currentThread().getName();
return name;
}
}
2、创建MyThreadFactory类实现ThreadFactory接口
public class MyThreadFactory implements ThreadFactory {
@Override
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
3、创建RejectedExecutionHandlerImpl 类实现 RejectedExecutionHandler接口
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("线程池拒绝了任务~");
System.err.println("线程池是" + executor);
System.err.println("线程任务是" + r);
}
}
4、创建CreateThread 类创建自定义线程池
public class CreateThread {
public static void main(String[] args) {
int coreSize = 2; // 核心线程是2
int maxSize = 4; // 最大线程是4
long keepAliveTime = 3;
TimeUnit time = TimeUnit.SECONDS; // 等待3秒
BlockingQueue queue = new LinkedBlockingQueue<>(3); // 阻塞队列为3
ThreadFactory factory = new MyThreadFactory();
RejectedExecutionHandler handler = new RejectedExecutionHandlerImpl();
ThreadPoolExecutor pool = new ThreadPoolExecutor(coreSize, maxSize, keepAliveTime, time, queue, factory, handler);
Runnable r = new RunnableImpl();
// 利用for循环提交8个任务,由于最大线程+阻塞队列最多七个,所有线程池会拒绝第八个任务
for (int i = 1; i <= 8; i++) {
// execute(Runnable r):提交任务
pool.execute(r);
}
}
}
六、需要说明的几点问题:
1、关于最大线程数量与核心线程数量的大小关系:
① 最大线程数 = 核心线程数:线程中的线程是固定的。
② 最大线程数 < 核心线程数:会发生IllegalArgumentExecption异常。
③ 最大线程数 > 核心线程数:线程池中的线程大于核心线程数并且阻塞队列已满时,会启用多余的线程。(该现象被称为线程膨胀)
2、最大线程池的生效说明:
① 当任务阻塞队列为无界队列时,设置不生效。
② 当任务阻塞队列为有界队列,且队列已满时,发生线程膨胀。
3、线程池存活时间的设置:
① 通过allowCoreThreadTimeOut方法设置对核心线程的回收时间(不建议使用)。
② 通过allowsCoreThreadTimeOut方法获取线程池的时间设置状态。
4、关于阻塞队列:
① 如果阻塞队列为无界队列时,不会产生线程膨胀。
② 当有界队列没有满时,不会产生线程膨胀。
③ 当有界队列已满,会产生线程膨胀。此时,超时设置生效。
5、任务线程与线程池线程的关系:
① 任务线程 <= 核心线程数:
核心线程会立即生效处理任务,线程池不膨胀,队列为空。
② 核心线程 < 任务线程 <= 核心线程数+阻塞队列长度:
核心线程会立即生效并处理任务,线程池不膨胀。
③ 核心线程数 + 阻塞队列长度 < 任务线程 <= 最大线程数 + 阻塞队列长度:
线程池最多膨胀到“最大线程数个”,并处理任务,剩下的在阻塞队列等待。
④ 任务线程 > 最大线程数 + 阻塞队列长度:
线程池最多接受“最大线程数 + 阻塞队列长度”个任务,并拒绝多余的任务。
为了更好的理解线程膨胀,我们举个例子:
比如:一个理发店有2名理发师+2名学徒,店里有个长沙发供顾客休息,沙发上最多坐3个人。很明显,两名理发师最多可以同时为2名顾客理发,当来了第3位顾客的时候,就让他坐在沙发上等候,如果顾客不断的来,沙发上已经坐满了。来第六个人的时候,理发师就叫来自己的学徒帮忙,学徒只有两位,所以这个理发店最多可以容纳七个人。当第八个人要来的时候,理发店就拒绝他进来。
上述例子中,
理发店 –> 线程池,
理发师 –> 核心线程,
顾客 –> 任务,
沙发 –> 阻塞队列,
学徒 –> 膨胀线程。