线程池:手动实现线程池

1.创建线程的问题

并发的本质是任务并行处理,大多数的并发程序围绕离散任务执行来构建。构建的基本思路是对任务进行划分,使得各个不同类型的任务之间相互独立,不存在依赖。这样就可以并行处理任意的任务。

在资源无限制的情况下,能够为每个任务构建一个线程来执行。但是实际情况是电脑的资源是有限的,构建过多线程后性能并不会一直提升,反而在达到峰值后开始衰减。

故为每个任务构建线程会有如下问题,

  1. 线程的创建和启动需要消耗资源,需要JVM和操作系统的支持。如果是十分简单的任务,创建线程的时间开销会比任务的逻辑运行时间还要长
  2. CPU性能有限,当大量线程一同竞争CPU时,造成系统的额外开销,很多线程无法争取到CPU,造成资源的浪费
  3. 系统能够支持的线程数有限,超出上限会导致崩溃

2.线程池概念

线程池的作用是维护一定数量的线程,接收的任务在线程池中并发执行。线程池基于生产者/消费者模式来实现,客户端调用线程池暴露的方法,想任务队列中添加任务,线程中的线程并发的从队列中取出任务。

3.自开发线程池

主体架构

自行开发一个基于生产者/消费者模式的线程池。具体构造如下,

  1. 线程池维护一个任务队列RunnableTaskQueue,将客户端通过调用线程池对外暴露的addTask方法将任务添加到该队列中
  2. 同时维护另一个线程队列,轮询的方式通过getTask方法取得任务队列中的任务并执行其逻辑

image
如果RunnableTaskqueue中无元素可被取出,线程就进入阻塞状态,直到有新的任务添加到队列中才会被唤醒。
image

实现任务队列—RunnableTaskQueue

使用LinkedList作为底层的数据结构,维护一个 Runnable 实现对象的队列。

public class RunnableTaskQueue {
	private final LinkedList<Runnable> tasks = new LinkedList<>();

	public Runnable getTask() throws InterruptedException {
		synchronized(tasks) {
			while (task.isEmpty()) {
				System.out.println(Thread.currentThread().getName() + " says task queue is empty. i will wait");
				tasks.wait();
			}
			return tasks.removeFirst();
		}
	}

	public void addTask(Runnable runnable) {
		synchronized(tasks) {
			tasks.add(runnable);
			tasks.notifyAll();
		}
	}
}

上面的代码实现了任务队列,通过同步代码块的形式保证了线程安全。

实现线程池—MyExecutor

public class MyExecutor {
	private final int poolSize;
	private final RunnableTaskQueue runnableTaskQueue;
	private final List<Thread> threads = new ArrayList<>();

	public MyExecutor(int poolSize) {
		this.poolSize = poolSize;
		this.runnableTaskQueue = new RunnableTaskQueue();
		for (int i=0; i<poolSize; i++) {
			this.initThread();
		}
	}

	private void initThread() {
		if (threads.size() <= poolSize) {
			Thread thread = new Thread(() -> {
				while(true) {
					try {
						Runnable task = runnableTaskQueue.getTask();
						task.run();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			);
			threads.add(thread);
			thread.start();
		}
	}

	public void Execute(Runnable runnable) {
		runnableTaskQueue.addTask(runnable);
	}
}

上面代码中关键方法是initThread,该方法的具体逻辑如下,

  1. 先检查线程数目是否达到poolSize,如果没有达到,则创建Thread并通过lambda表达式提供run的逻辑
  2. run方法的逻辑中不断从任务队列中获取任务,如果队列为空,会被阻塞
  3. 调用execute方法后,内部调用addTask方法加入到队列后对线程进行唤醒

运行线程池

1)调用形式1

public class Client {
    public static void main(String[] args) {
    	// 事先创建5个线程
        MyExecutor executor = new MyExecutor(5);

		// 按顺序提交10个Runnable
        Stream.iterate(1, item -> item + 1).limit(10).forEach(
            item -> {
                executor.execute(() -> {
                    try {
                        System.out.println(Thread.currentThread().getName() + " execute this task");
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        );
    }
}

执行结果,

Thread-0 says task queue is empty. i will wait
Thread-2 says task queue is empty. i will wait
Thread-1 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-4 says task queue is empty. i will wait // 创建5个线程,此时任务队列为空,线程阻塞
// 向线程池存放任务
Thread-4 execute this task
Thread-3 execute this task
Thread-0 execute this task
Thread-2 execute this task
Thread-1 execute this task
Thread-4 execute this task
Thread-0 execute this task
Thread-3 execute this task
Thread-2 execute this task
Thread-1 execute this task
// 10个任务执行完成后,5个线程再次进入阻塞态
Thread-2 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-0 says task queue is empty. i will wait
Thread-1 says task queue is empty. i will wait
Thread-4 says task queue is empty. i will wait

2)调用形式2

public class Client {
    public static void main(String[] args) {
        MyExecutor executor = new MyExecutor(5);

        Stream.iterate(1, item -> item + 1).limit(10).forEach(
            item -> {
                try {
                    if(item%2==0){
                        TimeUnit.SECONDS.sleep(2);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                executor.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " execute this task");

                });
            }
        );
    }
}

和方式一的区别是,客户端在 2 的整数倍时,休眠2毫秒再创建。另外任务中不再休眠。这样会造成生产得慢,消费得快的情形。执行结果如下,

Thread-0 says task queue is empty. i will wait
Thread-2 says task queue is empty. i will wait
Thread-1 says task queue is empty. i will wait
Thread-4 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait // 初始化5个线程

Thread-3 execute this task // 第1个任务被执行,第二个任务两秒后才入队
Thread-4 says task queue is empty. i will wait
Thread-1 says task queue is empty. i will wait
Thread-2 says task queue is empty. i will wait
Thread-0 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-3 execute this task // 第2个任务入队并被执行,第3个任务随后入队
Thread-2 says task queue is empty. i will wait
Thread-0 execute this task // 第3个任务入队后被执行,第4个任务两秒后才入队
Thread-1 says task queue is empty. i will wait
Thread-4 says task queue is empty. i will wait
Thread-0 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-3 execute this task // 第4个任务入队并被执行,第5个任务随后入队
Thread-0 execute this task // 第5个任务入队后被执行,第6个任务两秒后才入队
Thread-4 says task queue is empty. i will wait
Thread-1 says task queue is empty. i will wait
Thread-2 says task queue is empty. i will wait
Thread-0 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-3 execute this task // 第6个任务入队并被执行,第7个任务随后入队
Thread-2 says task queue is empty. i will wait
Thread-0 execute this task // 第7个任务入队后被执行,第8个任务两秒后才入队
Thread-1 says task queue is empty. i will wait
Thread-4 says task queue is empty. i will wait
Thread-0 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-3 execute this task // 第8个任务入队并被执行,第9个任务随后入队
Thread-4 says task queue is empty. i will wait
Thread-0 execute this task // 第9个任务入队后被执行,第10个任务两秒后才入队
Thread-1 says task queue is empty. i will wait
Thread-2 says task queue is empty. i will wait
Thread-0 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait
Thread-3 execute this task // 第8个任务入队并被执行
// 10个任务执行完成后,5个线程再次进入阻塞态
Thread-0 says task queue is empty. i will wait
Thread-2 says task queue is empty. i will wait
Thread-1 says task queue is empty. i will wait
Thread-4 says task queue is empty. i will wait
Thread-3 says task queue is empty. i will wait

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值