1.创建线程的问题
并发的本质是任务并行处理,大多数的并发程序围绕离散任务执行来构建。构建的基本思路是对任务进行划分,使得各个不同类型的任务之间相互独立,不存在依赖。这样就可以并行处理任意的任务。
在资源无限制的情况下,能够为每个任务构建一个线程来执行。但是实际情况是电脑的资源是有限的,构建过多线程后性能并不会一直提升,反而在达到峰值后开始衰减。
故为每个任务构建线程会有如下问题,
- 线程的创建和启动需要消耗资源,需要JVM和操作系统的支持。如果是十分简单的任务,创建线程的时间开销会比任务的逻辑运行时间还要长
- CPU性能有限,当大量线程一同竞争CPU时,造成系统的额外开销,很多线程无法争取到CPU,造成资源的浪费
- 系统能够支持的线程数有限,超出上限会导致崩溃
2.线程池概念
线程池的作用是维护一定数量的线程,接收的任务在线程池中并发执行。线程池基于生产者/消费者模式来实现,客户端调用线程池暴露的方法,想任务队列中添加任务,线程中的线程并发的从队列中取出任务。
3.自开发线程池
主体架构
自行开发一个基于生产者/消费者模式的线程池。具体构造如下,
- 线程池维护一个任务队列
RunnableTaskQueue
,将客户端通过调用线程池对外暴露的addTask
方法将任务添加到该队列中 - 同时维护另一个线程队列,轮询的方式通过
getTask
方法取得任务队列中的任务并执行其逻辑
如果RunnableTaskqueue
中无元素可被取出,线程就进入阻塞状态,直到有新的任务添加到队列中才会被唤醒。
实现任务队列—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
,该方法的具体逻辑如下,
- 先检查线程数目是否达到poolSize,如果没有达到,则创建Thread并通过lambda表达式提供
run
的逻辑 run
方法的逻辑中不断从任务队列中获取任务,如果队列为空,会被阻塞- 调用
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