我这两天看完了“无暴雨”老师讲的自己写一个线程池,以理解juc包里的线程池部分。自己跟着写了一个简单的线程池。
先来整理一下思路。附上代码和运行结果作证。最后将很巧妙的地方整理。
1.线程池原理
堆里的一个线程池对象,含有已经创建好的线程对象。当外部调用线程池对象提交任务的方法,线程池会将任务放到一个阻塞队列维护。当外部提交的任务超出队列元素上限,执行拒绝策略。拒绝策略有放弃这个任务、将拒绝策略异常抛出给执行提交任务的线程、提交任务的去执行任务。当线程任务队列中有任务,线程池中的线程会执行任务。运行一段时间后,队列中仍然有任务,线程池会创建新的线程,去执行任务。继续运行一段时间后,队列中仍然有任务,线程池会创建线程个数至上限。继续运行一段时间,队列中任务为0,线程池会销毁线程。
2.实现
ThreadPool接口:线程池需要有的功能。
package com.bjsxt.chapter24;
/**
* 线程池接口
*/
public interface ThreadPool {
// 关闭线程池
void shutdown();
// 提交任务到线程池
void execute(Runnable task);
// 查看线程是否被关闭
boolean isShutdown();
// 获取初始化的线程个数
int getInitSize();
// 获取核心线程数
int getCoreSize();
// 获取最大线程数
int getMaxSize();
// 获取活动线程数
int getActiveCount();
// 获取线程池中缓存任务队列大小
int getQueueSize();
}
RunnableQueue接口:
package com.bjsxt.chapter24;
/**
* 任务队列:主要用来缓存提交到线程池的任务
*/
public interface RunnableQueue {
// 入队
void offer(Runnable runnable);
// 出队
Runnable take();
// 队长度
int size();
}
DenyPolicy接口及其内部实现类:
package com.bjsxt.chapter24;
/**
* 拒绝策略
*/
@FunctionalInterface
public interface DenyPolicy {
// 为什么选择 任务,线程池?这两个作为参数?
void reject(Runnable runnable,ThreadPool threadPool);
// 自定义拒绝策略 直接蒋拒绝策略的实现类写在接口里面,我一般不会这样写,看到这样写之后,我想到以后我也可以尝试着使用
// 新的方式,在类比较多的情况下,蒋简单的实现类卸载他的借口中
// 直接讲任务丢弃
class DiscardDenyPolicy implements DenyPolicy{
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
System.out.println(runnable+" 任务已经被丢弃.");
}
}
// 向任务提交者抛出异常
class AbortDenyPolicy implements DenyPolicy{
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
throw new RunnableDenyException("任务 "+ runnable+ " 将被终止.");
}
}
// 任务提交者所在线程中执行任务
class RunnerDenyPolicy implements DenyPolicy{
@Override
public void reject(Runnable runnable, ThreadPool threadPool) {
// 线程池没关闭才执行任务
if(!threadPool.isShutdown())
runnable.run();
}
}
}
RunnableDenyException类:
package com.bjsxt.chapter24;
/**
* 拒绝策略中跑出的异常类
*/
public class RunnableDenyException extends RuntimeException {
public RunnableDenyException(String s) {
super(s);
}
}
ThreadFactory接口及其内部实现类:
package com.bjsxt.chapter24;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 创建线程的工厂!
* 线程的线程组,线程名称可以个性化的定义,thread-pool-0
*
* @FunctionalInterface 可以用lamda 8写
*/
@FunctionalInterface
public interface ThreadFactory {
Thread createThread(Runnable runnable);
// !再次的内部类的写法!
class DefaultThreadFactory implements ThreadFactory{
final AtomicInteger COUNTER=new AtomicInteger(0);
final AtomicInteger GROUP_COUNTER=new AtomicInteger(0);
// 给线程组加上后缀名字
final ThreadGroup GROUP=new ThreadGroup("myThreadGroupPool-"+GROUP_COUNTER.getAndIncrement());
@Override
public Thread createThread(Runnable runnable) {
int suffix=COUNTER.getAndIncrement();
return new Thread(GROUP,runnable,"my-Thread-Pool-thread-"+suffix);
}
}
}
InternalTask类:这个类真的让我惊叹!后面说原因。
package com.bjsxt.chapter24;
/**
* 任务类
* 取出缓存队列中的任务,执行
*/
public class InternalTask implements Runnable{
private final RunnableQueue runnableQueue;
private volatile boolean running=true;
public InternalTask(RunnableQueue runnableQueue){
this.runnableQueue=runnableQueue;
}
@Override
public void run() {
Runnable task=null;
// 如果当前任务为 running 并且没有中断,泽不断的从 queue 中获取 runnbale 执行
while(running && !Thread.currentThread().isInterrupted()){
task=runnableQueue.take();
task.run();
}
}
// 停止当前任务,主要会在线程池的 shutdown 方法中使用
public void stop(){
this.running=false;
}
}
LinkedRunnableQueue类:
package com.bjsxt.chapter24;
import java.util.LinkedList;
/**
* RunnableQueue:缓存任务的阻塞队列接口的实现类
*
* 缓存外面mian线程,或者其他线程执行execute(Runnable r),r任务的.
*/
public class LinkedRunnableQueue implements RunnableQueue {
// 链表 方便取第一个 删最后一个
private final LinkedList<Runnable> runnableQueue=new LinkedList<>();
// 上限
private final int MAX_SIZE;
// 拒绝策略 减轻用户负担 默认设置一个丢弃任务的拒绝策略
private DenyPolicy denyPolicy;
// 线程池引用变量 当执行拒绝策略的让提交任务的线程执行任务这个拒绝策略时,会判断线程池是否关闭,如果关闭的话,就说明用户不想执行任务了!所以会用到线程池。
private final ThreadPool threadPool;
public LinkedRunnableQueue(int MAX_SIZE, DenyPolicy denyPolicy, ThreadPool threadPool) {
this.MAX_SIZE = MAX_SIZE;
this.threadPool = threadPool;
this.denyPolicy=denyPolicy;
}
@Override
public void offer(Runnable runnable) {
synchronized (runnableQueue){
if (runnableQueue.size()>=MAX_SIZE){
denyPolicy.reject(runnable,threadPool);
return;// 过去写的是 wait,经测试发现问题。如果写的 wait 会做两遍 松鼠鳜鱼,因为 插不进来县城阻塞,当take所在线程唤醒,被执行,会从阻塞的pc中取指令,接着做菜。但是拒绝策略中已经扔掉、或者做了菜、或者抛出异常了。应该不做这个任务。
}
runnableQueue.addLast(runnable);
runnableQueue.notifyAll();
}
}
@Override
public Runnable take() {
synchronized (runnableQueue){
if (runnableQueue.size()<=0){// 如果链表里面数量是0,肯定也是不能取出数据的
try {
runnableQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
runnableQueue.notifyAll();
return runnableQueue.removeFirst();
}
}
@Override
public int size() {
synchronized (runnableQueue){
return runnableQueue.size();
}
}
}
BasicThreadPool类:ThreadPool的实现类
package com.bjsxt.chapter24;
import java.util.ArrayDeque;
import java.util.concurrent.TimeUnit;
public class BasicThreadPool extends Thread implements ThreadPool {
// 基本的数量 初始化线程个数、核心线程个数、最大线程个数、缓存任务队列的长度、线程池这个线程的启停变量
private final int initSize;
private final int coreSize;
private final int maxSize;
private int queueSize;
private boolean isRunning=true;
// 外面Main线程提交一个任务就放到任务队列里缓存。一个缓存任务的队列
private final RunnableQueue runnableQueue;
// 活跃线程列表
// 工作任务类
private ArrayDeque<ThreadTask> threadTaskList= new ArrayDeque<ThreadTask>();
private final long keepAliveTime;
private final TimeUnit timeUnit;
private final static DenyPolicy DEFAULT_DENY_POLICY = new DenyPolicy.DiscardDenyPolicy();
private class ThreadTask {
Thread thread;
InternalTask task;
public ThreadTask(Thread thread, InternalTask task) {
this.thread = thread;
this.task = task;
}
}
// 线程工厂
private final static ThreadFactory DEFAULT_THREAD_FACTORY=new ThreadFactory.DefaultThreadFactory();
private final ThreadFactory threadFactory;
// 构造器
// 给基本变量赋值
// 创建缓存任务队列
public BasicThreadPool(int initSize, int coreSize, int maxSize, int queueSize) {
this(initSize,coreSize,maxSize,queueSize,DEFAULT_THREAD_FACTORY,DEFAULT_DENY_POLICY,10,TimeUnit.SECONDS);
}
public BasicThreadPool(int initSize, int coreSize, int maxSize, int queueSize, ThreadFactory threadFactory,
DenyPolicy denyPolicy,long keepAliveTime,TimeUnit timeUnit){
this.initSize=initSize;
this.coreSize=coreSize;
this.maxSize=maxSize;
this.queueSize=queueSize;
this.threadFactory=threadFactory;
this.keepAliveTime=keepAliveTime;
this.timeUnit=timeUnit;
this.runnableQueue=new LinkedRunnableQueue(queueSize,denyPolicy,this);
init();
}
// 启动自维护
// 创建线程
private void init() {
start();
createThread(initSize);
}
private void createThread(int num) {
int i=0;
while (i<num){
// 线程池内部任务,循环获取每一个缓存的任务
InternalTask task = new InternalTask(runnableQueue);
Thread thread = DEFAULT_THREAD_FACTORY.createThread(task);
ThreadTask threadTask = new ThreadTask(thread,task);
threadTaskList.addLast(threadTask);
// 启动线程
thread.start();
i++;
}
}
@Override
public void run() {
while (!isShutdown() && !this.isInterrupted()) {
try {
// 每5s进行一次自维护,第一次是在调用了构造方法后的5s维护
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){// 同时操作 threadTaskList 会导致数据不一致。与shutdown方法
if(runnableQueue.size()>0 && initSize<coreSize){
createThread(coreSize-initSize);
continue;
}
if(runnableQueue.size()>0 && coreSize<maxSize){
createThread(maxSize-coreSize);
}
if(runnableQueue.size()==0 && threadTaskList.size()>coreSize){
System.out.println(threadTaskList.size()-coreSize);
removeThread(threadTaskList.size()-coreSize);//
}
}
}
}
private void removeThread(int num) {
if(!isShutdown() && !isInterrupted()){
while(num!=0){
ThreadTask threadTask = threadTaskList.removeFirst();
System.out.println(threadTask.thread.getName()+" will remove.");
threadTask.task.stop();
// threadTask.thread.interrupt();
num--;
}
}
}
@Override
public void shutdown() {
if(isShutdown())
return;
synchronized (this){
isRunning=false;
// 关闭线程池的资源
for(ThreadTask threadTask:threadTaskList){
threadTask.task.stop();
System.out.println(threadTask.thread.getName()+" will end. ");
}
System.out.println("thread pool thread shutdown end. ");
}
}
@Override
public void execute(Runnable task) {
if(this.isShutdown())
throw new IllegalStateException("thread pool already shutdown.");
runnableQueue.offer(task);
}
@Override
public boolean isShutdown() {
return !isRunning;
}
@Override
public int getInitSize() {
if(this.isShutdown()) {
throw new IllegalStateException("thread pool already shutdown.");
}
return initSize;
}
@Override
public int getCoreSize() {
if(this.isShutdown()) {
throw new IllegalStateException("thread pool already shutdown.");
}
return coreSize;
}
@Override
public int getMaxSize() {
if(this.isShutdown()) {
throw new IllegalStateException("thread pool already shutdown.");
}
return maxSize;
}
@Override
public int getActiveCount() {
if(this.isShutdown()) {
throw new IllegalStateException("thread pool already shutdown.");
}
return threadTaskList.size();
}
@Override
public int getQueueSize() {
if(this.isShutdown()) {
throw new IllegalStateException("thread pool already shutdown.");
}
return runnableQueue.size();
}
}
测试类:
使用XP编程的思想:测试一点,写能过测试的代码,如此重复,通过测试,完成功能。
test1:线程池运行做菜的任务。能做菜即可。
package com.bjsxt.chapter24.test;
import com.bjsxt.chapter24.BasicThreadPool;
import com.bjsxt.chapter24.ThreadPool;
import java.util.concurrent.TimeUnit;
public class Main01 {
public static void main(String[] args) {
// 线程池的创建
ThreadPool threadPool = new BasicThreadPool(3,5,5,5);
// 模拟的做菜任务
String [] deliciousFood = {"糖醋鱼","清蒸鱼","酸菜鱼","麻辣鱼","松鼠鳜鱼"};
// 任务的提交
for(int i=0;i<5;i++){
int index=i;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " will make " + deliciousFood[index] +"."+"(runnable address is "+this+")");
System.out.println("-------------------");
try {
// 模拟厨师做一道菜的时间 3s
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " maked " + deliciousFood[index]+"."+"(runnable address is "+this+")");
}
};
threadPool.execute(runnable);
}
}
}
运行结果:
test2:缓存任务满了,拒绝策略3种的测试
package com.bjsxt.chapter24.test;
import com.bjsxt.chapter24.BasicThreadPool;
import com.bjsxt.chapter24.ThreadPool;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
/**
* test 2 缓存任务满了,拒绝策略3种的测试
*/
public class Main02 {
public static void main(String[] args) {
ThreadPool threadPool=new BasicThreadPool(2,3,5,3);
String [] deliciousFood = {"糖醋鱼","清蒸鱼","酸菜鱼","麻辣鱼","松鼠鳜鱼"};
for(int i=0;i<deliciousFood.length;i++){
int val=i;
Runnable task=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ " will make "+deliciousFood[val]+"(task address is "+this+")");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " maked "+deliciousFood[val]+"(task address is "+this+")");
}
};
threadPool.execute(task);
}
}
}
运行结果
test3:
package com.bjsxt.chapter24.test;
import com.bjsxt.chapter24.BasicThreadPool;
import com.bjsxt.chapter24.ThreadPool;
import java.util.concurrent.TimeUnit;
/**
* test3 线程池销毁
* 5道菜的任务,2个厨子,每个厨子做一个菜要3秒。
* 2秒后销毁后线程池,之后不应该做菜。有三道菜是没有做的。
*/
public class Main03 {
public static void main(String[] args) {
ThreadPool threadPool=new BasicThreadPool(2,3,5,3);
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPool.shutdown();
},"end-Thread").start();
String [] deliciousFood = {"糖醋鱼","清蒸鱼","酸菜鱼","麻辣鱼","松鼠鳜鱼"};
for(int i=0;i<deliciousFood.length;i++){
int val=i;
Runnable task=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ " will make "+deliciousFood[val]+"(task address is "+this+")");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " maked "+deliciousFood[val]+"(task address is "+this+")");
}
};
threadPool.execute(task);
}
}
}
运行结果
执行stop,依然炒完菜了,是因为InternalTask里面run里面的循环,取完任务后一定会执行他的run.
test4:
package com.bjsxt.chapter24.test;
import com.bjsxt.chapter24.BasicThreadPool;
import com.bjsxt.chapter24.ThreadPool;
import java.util.concurrent.TimeUnit;
/**
* test4 线程池自维护测试
*/
public class Main04 {
public static void main(String[] args) {
// 构造器 创建最大缓冲数量为3个任务的缓冲队列,创建1个厨师
ThreadPool threadPool = new BasicThreadPool(1,3,5,10);
String [] deliciousFood = {"糖醋鱼","清蒸鱼","酸菜鱼","麻辣鱼","松鼠鳜鱼","糖醋鱼1","清蒸鱼1","酸菜鱼1","麻辣鱼1","松鼠鳜鱼1"};
for(int i=0;i<deliciousFood.length;i++){
int val=i;
Runnable task=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" will make "+deliciousFood[val]);
try {
// 做菜时间 3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" maked "+deliciousFood[val]);
}
};
// 任务提交到线程池,线程池提交到缓冲队列
threadPool.execute(task);
}
while(true){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------------------");
System.out.println("thread pool 's initSize : "+threadPool.getInitSize());
System.out.println("thread pool 's coreSize : "+threadPool.getCoreSize());
System.out.println("thread pool 's maxSize : "+threadPool.getMaxSize());
System.out.println("thread pool 's activeCount : "+threadPool.getActiveCount());
System.out.println("thread pool 's queueSize : "+threadPool.getQueueSize());
System.out.println("-----------------------------");
}
}
}
运行结果:
两次扩容,initSize:1->coreSize:3->maxSize:5
销毁到coreSize:3
3.总结
线程池是堆里的一个对象,这个对象里面维护一个队列引用变量,缓存外部提交到线程池中的任务。如果他的长度是5,只能缓存五个菜谱。如果再来一个菜谱,就会执行拒绝策略。
(1)InternalTask
过去我会为每一个任务创建一个线程对象,调用start执行任务。也就是说会放5个厨师去炒5道菜!线程对象创建会占用堆空间,线程对象调用start方法jvm会准备程序计数器,java虚拟机栈,本地方法栈的内存资源。即便是将线程对象的创建,start调用写在循环中,当一次循环结束这个线程对象就会被回收。这样vm仍然会分配线程私有的内存资源。回收机制也在执行。
但是看见这个类里面这样写:
public void run() {
Runnable task=null;
// 如果当前任务为 running 并且没有中断,泽不断的从 queue 中获取 runnbale 执行
while(running && !Thread.currentThread().isInterrupted()){
task=runnableQueue.take();
task.run();
}
}
仔细观察,只创建一个线程对象并启动,就可以执行这个内部类的run方法,就会获取每一个外部提交的任务的对象,并执行其中的业务逻辑。这样过去创建5个线程,启动5个线程,回收5个线程的工作量,就被降低到创建一个线程,启动一个线程,回收一个线程啦!
(2)ThreadFactory
内部类的写法,这样看起来会比较简便。以及为什么过去使用juc包下的线程池,名字都与Thread的默认名称不同,是因为在他的实现类中可以通过创建Thread,构造器穿进去个性化的名称。
final AtomicInteger COUNTER=new AtomicInteger(0);
final AtomicInteger GROUP_COUNTER=new AtomicInteger(0);
// 给线程组加上后缀名字
final ThreadGroup GROUP=new ThreadGroup("myThreadGroupPool-"+GROUP_COUNTER.getAndIncrement());
@Override
public Thread createThread(Runnable runnable) {
int suffix=COUNTER.getAndIncrement();
return new Thread(GROUP,runnable,"my-Thread-Pool-thread-"+suffix);
}
(3)BasicThreadPool
为什么自维护逻辑里面和shutdown逻辑里面要加synchronized?
因为他们都会取线程池启停变量,都有可能操作活动线程队列,删除元素、访问元素。如果不加锁,一边删除,一边访问,数据是不一致的。
public void shutdown() {
if(isShutdown())
return;
synchronized (this){
isRunning=false;
// 关闭线程池的资源
for(ThreadTask threadTask:threadTaskList){
threadTask.task.stop();
System.out.println(threadTask.thread.getName()+" will end. ");
}
System.out.println("thread pool thread shutdown end. ");
}
}
public void run() {
while (!isShutdown() && !this.isInterrupted()) {
try {
// 每5s进行一次自维护,第一次是在调用了构造方法后的5s维护
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){// 同时操作 threadTaskList 会导致数据不一致。与shutdown方法
if(runnableQueue.size()>0 && initSize<coreSize){
createThread(coreSize-initSize);
continue;
}
if(runnableQueue.size()>0 && coreSize<maxSize){
createThread(maxSize-coreSize);
}
if(runnableQueue.size()==0 && threadTaskList.size()>coreSize){
System.out.println(threadTaskList.size()-coreSize);
removeThread(threadTaskList.size()-coreSize);//
}
}
}
}
使用throw可以不用返回值。
public int getInitSize() {
if(this.isShutdown()) {
throw new IllegalStateException("thread pool already shutdown.");
}
return initSize;
}
意思就是不会获得值,仅仅会抛出异常。
public class Main {
public static void main(String[] args) {
int i=get();
System.out.println(i);
}
private static int get() {
if(true)
throw new IllegalStateException("no state");
return 0;
}
}