1、线程池
线程池就是一个可以复用线程的技术。
2、应用场景
用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
3、线程池工作原理
从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
4、创建线程池
java在jdk5.0起提供了代表线程池的接口:ExecutorService。
- 方式一:使用ExecutService的实现类ThreadPoolExecutor自创建一个线程池对象。
- 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
4.1、ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
参数说明:
- 参数一:
corePoolSize
:指定线程池的核心线程数量。 - 参数二:
maximumPoolSize
:指定线程池的最大线程数量。 - 参数三:
keepAliveTime
:指定临时线程的存活时间。 - 参数四:
unit
:指定临时线程存活的时间单位(秒,分,时,天) - 参数五:
workQueue
:指定线程池的任务队列 - 参数六:
threadFactory
:指定线程池的线程工厂 - 参数七:
handler
:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了怎么处理)
//1、通过ThreadPoolExecutor来创建线程池ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
注意事项
1、临时线程什么时候创建
新任务提交时,发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
2、什么时候开始会拒绝新任务
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
方法名称 | 说明 |
---|---|
void execute(Runnable command) | 执行Runnable任务 |
Future<T> submit(Callable<T> task) | 执行Callable任务,返回未来任务对象,用于获取线程返回的结果 |
void shutdown() | 等全部任务执行完毕后,再关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 |
5、线程池处理Runnable任务
public class ThreadPoolTest1 {public static void main(String[] args) {//1、通过ThreadPoolExecutor来创建线程池ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());MyRunnable myRunnable = new MyRunnable();//线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(myRunnable);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(myRunnable);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(myRunnable);//复用前面的核心任务pool.execute(myRunnable);//复用前面的核心任务pool.execute(myRunnable);//增加线程,超出任务队列
// pool.execute(myRunnable);
// pool.execute(myRunnable);//线程池中的三个主线程已经被占用,线程队列已满//此时再创建线程,就会创建临时线程
// pool.execute(myRunnable);//等线程任务结束后关闭线程池pool.shutdown();//立即关闭线程池
// pool.shutdownNow();}
}public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"===>666");try {Thread.sleep(2000);//设置休眠时间,便于查看
// Thread.sleep(Integer.MAX_VALUE);//设置最大时间,用来测试临时线程的创建} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
执行结果
pool-1-thread-1===>666
pool-1-thread-3===>666
pool-1-thread-2===>666
pool-1-thread-1===>666
pool-1-thread-2===>666
5.1、临时线程创建时机
public class ThreadPoolTest1 {public static void main(String[] args) {//1、通过ThreadPoolExecutor来创建线程池ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());MyRunnable myRunnable = new MyRunnable();//线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(myRunnable);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(myRunnable);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的pool.execute(myRunnable);//复用前面的核心任务pool.execute(myRunnable);//复用前面的核心任务pool.execute(myRunnable);//增加线程,超出任务队列pool.execute(myRunnable);pool.execute(myRunnable);//线程池中的三个主线程已经被占用,线程队列已满//此时再创建线程,就会创建临时线程pool.execute(myRunnable);//等线程任务结束后关闭线程池
// pool.shutdown();//立即关闭线程池
// pool.shutdownNow();}
}
public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"===>666");try {
// Thread.sleep(2000);//设置休眠时间,便于查看Thread.sleep(Integer.MAX_VALUE);//设置最大时间,用来测试临时线程的创建} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
执行结果:会创建临时线程4
pool-1-thread-1===>666
pool-1-thread-3===>666
pool-1-thread-2===>666
pool-1-thread-4===>666
如果再加一个,就会创建临时线程5
pool-1-thread-2===>666
pool-1-thread-4===>666
pool-1-thread-3===>666
pool-1-thread-1===>666
pool-1-thread-5===>666
再增加一个,就会超出线程池的限定,此时就会按照设置的方法进行处理
参数七:handler
:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了怎么处理)
新任务拒绝策略:
策略 | 详解 |
ThreadPoolExecutor.AbortPolicy() | 不执行此任务,而且直接抛出一个运行时异常 |
ThreadPoolExecutor.DiscardPolicy() | 新任务被提交后直接被丢弃掉,并且不会抛出异常,无法感知到这个任务会被丢弃,可能造成数据丢失。 |
ThreadPoolExecutor.DiscardOldestPolicy() | 会丢弃任务队列中的头结点,通常是存活时间最长并且未被处理的任务。 |
ThreadPoolExecutor.CallerRunsPolicy() | 当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。不会抛出异常。 |
6、线程池处理Callable任务
public class ThreadPoolTest2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//1、通过ThreadPoolExecutor来创建线程池ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8,TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());Future f1 = pool.submit(new MyCallable(100));Future f2 = pool.submit(new MyCallable(200));Future f3 = pool.submit(new MyCallable(300));Future f4 = pool.submit(new MyCallable(400));System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}
}public class MyCallable implements Callable {private int n;public MyCallable(int n) {this.n = n;}@Overridepublic String call() throws Exception {int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return Thread.currentThread().getName()+"求出了1-"+n+"的和是:" + sum;}
}
执行结果:
pool-1-thread-1求出了1-100的和是:5050
pool-1-thread-2求出了1-200的和是:20100
pool-1-thread-3求出了1-300的和是:45150
pool-1-thread-3求出了1-400的和是:80200
7、Executors工具类实现线程池
方法名称 | 说明 |
newFixedThreadPool(int nThreads) | 创建固定数量线程的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
newSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果改线程出现异常而结束,那么线程池会补充一个新线程。 |
newCachedThreadPool | 线程数量随着人物增加而增加,如果线程任务执行完毕且空闲了60秒则会被回收掉 |
newScheduledThreadPool | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务 |
public class ThreadPoolTest3 {public static void main(String[] args) throws ExecutionException, InterruptedException {//1、通过ThreadPoolExecutor来创建线程池
// ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8,
// TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.CallerRunsPolicy());// ExecutorService pool = Executors.newFixedThreadPool(3);
// ExecutorService pool = Executors.newSingleThreadExecutor();
// ExecutorService pool = Executors.newCachedThreadPool();ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);Future f1 = pool.submit(new MyCallable(100));Future f2 = pool.submit(new MyCallable(200));Future f3 = pool.submit(new MyCallable(300));Future f4 = pool.submit(new MyCallable(400));System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}
}
8、核心线程数的配置
- 计算密集型的任务:核心线程数量=CPU核数 +1
- IO密集的任务:核心数量=CPU核数 * 2
9、不建议使用Executors创建线程池
-
缺乏对线程池的精细控制:
Executors
提供的方法通常创建一些简单的线程池,如固定大小的线程池、单线程线程池等。然而,这些线程池的配置通常是有限制的,难以进行进一步的定制和优化。 -
可能引发内存泄漏:一些
Executors
创建的线程池,特别是FixedThreadPool
和SingleThreadExecutor
,使用无界队列来存储等待执行的任务。这意味着如果任务提交速度远远快于任务执行速度,队列中可能会积累大量未执行的任务,可能导致内存泄漏。 -
不易处理异常:
Executors
创建的线程池默认使用一种默认的异常处理策略,通常只会将异常打印到标准输出或记录到日志中,但不会提供更多的控制。这可能导致异常被忽略或无法及时处理。 -
不支持线程池的动态调整:某些线程池应该支持动态调整线程数量以应对不同的负载情况。
Executors
创建的线程池通常是固定大小的,不容易进行动态调整。 -
可能导致不合理的线程数目:一些
Executors
方法创建的线程池默认将线程数目设置为非常大的值,这可能导致系统资源的浪费和性能下降。
因此,对于生产环境中的应用程序,通常建议使用 ThreadPoolExecutor
类直接创建和配置线程池,以便更精确地控制线程池的各个参数,包括核心线程数、最大线程数、队列类型、拒绝策略等。这样可以更好地满足应用程序的需求,并确保线程池在不同负载情况下表现良好。当然,在某些情况下,Executors
创建的简单线程池可能足够使用,但需要谨慎考虑其限制和适用性。