Java多线程并发编程-线程池
- 线程池
- 问题思考
- 线程池原理
- 任务用什么表示
- 仓库用什么:BlockingQueue
- 自己实现一个线程池
- JDK线程池API
- Executor
- ExecutorService
- ScheduledExecutorService
- Callable
- Future
- ThreadPoolExecutor
- Executors
线程池
问题思考
- 问题1、用多线程的目的是什么?
充分利用 CPU 资源,并发做多件事。
- 问题2、单核 CPU 机器上适不适合用多线程?
适合,如果是单线程,线程中需要等待 IO 时,此时 CPU 就空闲出来了。
- 问题3、线程什么时候会让出 CPU?
阻塞时,wait,await,等待IO,sleep,yield,执行结束了。。
- 问题4、线程是什么?
执行任务的基本单位。一条代码执行流,完成一组代码的执行。这一组代码,我们往往称为一个任务。
- 问题5、CPU 做的是什么工作?
执行代码
- 问题6、线程是不是越多越好?
造卡车(线程)要不要时间?一次使用,用完了得销毁,销毁要不要耗时间?
1、线程在java中是一个对象,每一个java线程都需要有一个操作系统线程支持。线程创建、销毁需要时间。如果 创建时间+销毁时间 > 执行任务时间 就很不合算。造很多的卡车,得需要空间来放它们,会不会造成内存紧张?
2、java对象占用堆内存,操作系统线程占用系统内存,根据 jvm 规范,一个线程默认最大栈大小 1M,这个栈空间是需要从系统内存中分配的。
线程过多,会消耗很多内存
3、操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。
- 问题7、该如何正确使用多线程
多线程目的:充分利用 CPU 并发做事
线程的本质:将代码送给 CPU 执行
用合适数量的卡车不断运送代码即可,这个合适数量的线程就构成了一个池。
有任务要执行,就放入池中,池中的一个线程将把任务运送到 CPU 执行。
线程池原理
- 接收任务,放入仓库
- 工作线程从仓库取任务,执行
- 当没有任务时,线程阻塞,当有任务时唤醒线程执行
任务用什么表示
- Runnable
- Callable
仓库用什么:BlockingQueue
- 阻塞队列,线程安全的:在队列为空时获取阻塞,在队列满时放入阻塞。
- BlockingQueue的方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出异常,第二种是返回一个特殊值(null或false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。总结如下表:
方法/处理方法 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
插入方法 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移出方法 | remove() | poll() | take() | poll(time, unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
- 问题8、如何确定合适数量的线程
如果是计算型任务?
CPU数量的1-2倍如果是IO型任务?
需啊哟多一些线程,要根据具体IO阻塞时长进行考量决定。如 tomcat 中默认的最大线程数为200。也可以考虑根据需要在一个最小数量和最大数量间自动增减线程数。
自己实现一个线程池
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import static java.lang.System.out;
/**
* @description: 自己实现一个线程池
* @author: 10217629
* @date: 2019/4/12
*/
public class FixedSizeThreadPool {
// 思考:如果我们需要手写一个线程池,需要什么东西?
// 1. 需要一个仓库
private BlockingQueue<Runnable> blockingQueue;
// 2. 需要一个线程集合
private List<Thread> workers;
// 3. 需要一个干活的线程
public static class Worker extends Thread {
private FixedSizeThreadPool pool;
public Worker(FixedSizeThreadPool pool) {
this.pool = pool;
}
@Override
public void run() {
// 去仓库拿东西
while (this.pool.isWorking || this.pool.blockingQueue.size() > 0) {
Runnable task = null;
try {
if (this.pool.isWorking) {
// 阻塞方式
task = this.pool.blockingQueue.take();
} else {
// 非阻塞方式
task = this.pool.blockingQueue.poll();
}
} catch (Exception e) {
e.printStackTrace();
}
if (task != null) {
task.run();
out.println("线程:" + Thread.currentThread().getName() + "执行完毕");
}
}
}
}
// 4. 需要初始化规定好仓库的大小以及集合,同时把线程准备就绪
public FixedSizeThreadPool(int poolSize, int taskSize) {
if (poolSize <= 0 || taskSize <= 0) {
throw new IllegalArgumentException("非法参数");
}
this.blockingQueue = new LinkedBlockingQueue<Runnable>(taskSize);
// Collections.synchronizedList:处理线程安全
this.workers = Collections.synchronizedList(new ArrayList<Thread>());
for (int i = 0; i < poolSize; i++) {
Worker worker = new Worker(this);
worker.start();
workers.add(worker);
}
}
// 5. 需要向仓库放的代码(阻塞)
public void excute(Runnable task) {
try {
this.blockingQueue.put(task);
} catch (Exception e) {
e.printStackTrace();
}
}
// 6. 需要向仓库放的代码(非阻塞)
public boolean submit(Runnable task) {
if (this.isWorking) {
return this.blockingQueue.offer(task);
} else {
return false;
}
}
// 7. 需要一个关闭方法
// a. 关闭的时候,仓库要停止新的线程进来
// b. 关闭的时候,如果仓库没有东西,我们要执行完
// c. 关闭的时候,如果去仓库拿东西,我们就不能阻塞了
// d. 关闭的时候,把阻塞的线程全部中断
private volatile boolean isWorking = true;
public void shutdown() {
this.isWorking = false;
for (Thread thread : workers) {
if (thread.getState().equals(Thread.State.WAITING) || thread.getState().equals(Thread.State.BLOCKED)) {
thread.interrupt();
}
}
}
// 测试
public static void main(String[] args) {
FixedSizeThreadPool pool = new FixedSizeThreadPool(3, 6);
for (int i = 0; i < 6; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
out.println("一个线程被放入到我们的仓库中...");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
out.println("有线程被中断");
}
}
});
}
pool.shutdown();
}
}
JDK线程池API
- Java并发包提供了丰富的线程池实现
Executor
- Executor 接口:
ExecutorService
- ExecutorService 加入了关闭方法和对Callable、Future的支持:
ScheduledExecutorService
- ScheduledExecutorService 加入对定时任务的支持:
Callable
- Callable 对 Runnable 的改进:可以返回值,可以抛出异常:
Future
- Future 异步任务监视器,让提交者可以监控任务的执行:
ThreadPoolExecutor
- ThreadPoolExecutor 线程池标准实现:
Executors
- Executors 创建线程池的工厂类,快速得到线程池的工具类,减轻我们的任务,它的工厂方法:
- newFixedThreadPool(int nThreads):创建一个固定大小、任务队列容量无界的线程池。池的核心线程数 = 最大线程数 = nThreads。
- newCachedThreadPool():创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,池中的核心线程数 = 0 最大线程数 = Integer.MAX_VALUE。
- newSingleThreadExecutor():只有一个任务队列的单一线程池。该线程池确保任务按加入的顺序一个一个依次执行,任何时刻只有一个线程来执行无界队列的任务。当唯一的线程因任务异常终止时,将创建一个新的线程类继续执行后续的任务。单一线程与 newFixedThreadPool的区别在于:单一线程池的大小是不能再改变的。
- newScheduledThreadPool(int corePoolSize):能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数 = Integer.MAX_VALUE。
- newWorkStealingPool():以当前系统可用处理器数作为并行级别创建的 work-stealing thread pool。
- newWorkStealingPool(int parallelism):以 parallelism 指定的并行级别创建的 work-stealing thread pool(ForkJoinPool)。