问题
一个线程大约占用1MB的内存空间,一个项目中我们可以创建多个线程.当项目中有多个线程,此时会占据大量的内存,甚至可能会导致内存溢出
解决思路
1,限制线程的创建数量
2,能复用的线程尽量复用
3,快速回收使用完毕的线程
线程池简介
JDK提供的对线程优化的体系,其中包含了对线程的创建,回收,复用的处理,也可以存储线程
使用线程池的主要优势
重用线程:线程池会维护一组预先创建的线程,可以重复使用它们来执行多个任务,避免了频繁创建和销毁线程的开销。
控制并发数:线程池可以限制同时执行的线程数量,防止线程过多导致系统资源耗尽。
管理线程生命周期:线程池负责创建、启动、暂停、恢复和销毁线程,简化了线程的管理和监控。
提供任务队列:线程池通常还提供一个任务队列,用于存储等待执行的任务,避免任务丢失或阻塞。
使用线程池
步骤:
- 创建线程池对象
- 提交任务给线程池执行
- 关闭线程池
线程池体系
Executor(接口)
ExecutorService(接口)
AbstractExecutorService(抽象类)
ThreadPoolExecutor(普通类)
ScheduledExecutorService(接口)
Executor提供的方法
void execute(Runnable command);
作用:执行给定的线程任务
Executors作用
JDK提供方便我们创建线程池的工具类
提供创建线程的方法
创建可变线程池
//可变线程池特点:
//线程数量不定,
//当线程池中没有空闲线程,该线程就会新建一个线程用于执行该任务
//如果当空闲线程60秒时间后没有执行任务将会回收该线程
//ExecutorService pool = Executors.newCachedThreadPool();
创建固定线程池
//固定线程池特点:线程数量恒定
//ExecutorService pool = Executors.newFixedThreadPool(3);
创建单例线程池
//单例线程池:线程池中只有一个线程
//ExecutorService pool = Executors.newSingleThreadExecutor();
创建调度线程池
//调度线程池特点:线程池中的任务可以延迟执行,也可以延迟重复执行
//ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
创建单例调度线程池
//单例调度线程池特点:调度线程池只有一个线程
//ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();
创建抢占线程池
//特点:提高执行效率(没实现,只是构想)
ExecutorService pool = Executors.newWorkStealingPool();
ExecutorService提供的方法
void shutdown();
作用:关闭线程池
boolean isShutdown();
作用:获取线程池是否关闭
boolean isTerminated();
作用:获取线程池是否已经终止
Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
作用:提交线程任务
ThreadPoolExecutor构造函数
ThreadPoolExecutor(int corePoolSize,//核心线程数量,最小线程数量
int maximumPoolSize,//最大线程数量
long keepAliveTime,//回收时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//存储线程任务的队列
ThreadFactory threadFactory,//创建线程的工厂
RejectedExecutionHandler handler)//复用算法阿里白皮书:
为了加深对线程池的理解,建议使用ThreadPoolExecutor创建线程池
提供的方法:==
getActiveCount() :返回线程池中正在执行任务的线程数量。
getCompletedTaskCount() :返回线程池已经完成的任务数量。
getCorePoolSize() :返回线程池的核心线程数量。
getLargestPoolSize() :返回线程池曾经创建过的最大线程数量。
getPoolSize() :返回线程池当前的线程数量。
getQueue() :返回线程池的任务队列,可以通过这个方法查看任务队列的长度和内容等信息。
ScheduledExecutorService
简介
中文名:调度线程池
特点:可以使任务延迟执行或延迟重复执行,调度线程池关闭时,其重复执行的任务也会被关闭
特有的方法
public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit)
public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit)
作用:延迟执行
参数:
1,线程任务
2,延迟时间
3,时间单位public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit)
作用:延迟重复执行
参数:
1,线程任务
2,延迟时间
3,间隔时间
上一次任务开始时间 - 本次任务开始时间 = 间隔时间
4,时间单位
注意:
当任务执行时间大于间隔时间,此时上次任务结束时,直接开始执行本次任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit)
作用:延迟重复执行
参数:
1,线程任务
2,延迟时间
3,间隔时间
上一次任务结束时间 - 本次任务开始时间 = 间隔时间
4,时间单位
Lock
作用: 优化锁; 更加简便的,明了的使用锁
体系与方法
提供的方法
void lock():关锁
void unlock():释放锁
Condition newCondition();//获取锁对象
子类:
ReentrantLock(重写锁)Condition
提供的方法
void await():类似与Object提供的wait方法
void signal();类似与Object提供的notify方法
void signalAll();类似与Object提供的notifyAll方法ReadWriteLock(读写锁)
提供的方法
Lock readLock(); 获取读锁
Lock writeLock(); 获取写锁
特点:
读-读 不互斥,可以同时进行
读-写 互斥,不能同时进行
写-写 互斥,不能同时进行
作用:当多个线程操作同一个数据时,提高了效率
线程优化
线程池大小的选择:
线程池大小应该根据系统处理能力和任务特性来设置。如果系统是CPU密集型,即任务需要长时间占用CPU资源,那么应该将线程池大小设置为CPU核心数,以充分利用CPU资源;如果系统是IO密集型,即任务需要等待IO操作完成才能继续执行,那么可以适当增加线程池大小,以更好地利用空闲时间处理IO操作。
队列容量的选择:
线程池的队列容量也很重要,如果队列容量太小,可能会导致任务被拒绝或者处理速度变慢;如果队列容量太大,可能会导致内存消耗过大。因此,需要根据任务的特点和系统负载情况来选择合适的队列容量。
线程池参数的调整:
线程池还有一些其他参数可以进行调整,比如线程空闲时间、拒绝策略等。线程空闲时间指的是线程在空闲一段时间后被销毁的时间阈值,可以通过设置这个参数来限制线程池中的线程数量。拒绝策略是指当线程池中的任务队列已满或无法接受新任务时,应该如何处理新任务。常见的拒绝策略包括直接抛弃、丢到调用线程执行、抛出异常等。
监控和优化:
线程池的监控和优化也非常重要,可以通过监控线程池的运行状态、任务队列长度、线程池活动线程数等指标,及时发现问题并进行优化。比如,如果发现线程池中的线程被频繁创建和销毁,可能说明线程池大小设置不合理;如果发现任务队列长度过长,可能说明线程池处理能力不足,需要增加线程池大小或者调整任务队列容量等。
总之,线程池调优需要结合具体的业务场景和系统需求进行,需要根据实际情况选择合适的线程池大小、队列容量和参数,以及进行监控和优化。