一个线程池通常有如下几部分组成,核心线程池,非核心线程池和工作队列。
核心线程池中的线程会一直存在,空闲时不会被回收;非核心线程池中的线程在空闲一定时间后可能被回收;工作队列存放线程池中暂时无法处理执行的任务。
Java线程池说明
Java定义了Executor接口并在该接口中定义了execute( )用于执行一个线程任务,然后通过ExecutorService实现Executor接口并执行具体的线程操作。ExecutorService接口有多个实现类可用于创建不同的线程池。
newCachedThreadPool
newCachedThreadPool用于创建一个缓存线程池。之所以叫缓存线程池,是因为它在创建新线程时,如果有可重用的线程,则重用他们,否则重新创建一个新的线程并将其添加到线程池中。对于执行时间很短的任务惹眼,newCachedThreadPool线程池能很大程度地重用线程进而提高系统的性能。
在线程池的keepAliveTime时间超过默认的60秒后,该线程会被终止并从缓存中移除,因此在没有线程任务运行时,newCachedThreadPool将不会占用系统的线程资源。
在创建线程时需要执行申请CPU和内存、记录线程状态、控制阻塞等多项工作,复杂且耗时。因此,在有执行时间很短的大量任务需要执行的情况下,newCachedThreadPool能够很好地复用运行中的线程(任务已经完成但未关闭的线程)资源来提高系统的运行效率。
* 创建一个线程池,根据须要创建多个线程,
* 但是会重复使用之前创建好的且已经空闲的线程资源。
* 这个线程池通常会提高执行许多短暂异步任务的程序的性能。
* 调用execute()方法将重用以前构造的线程(如果可用)。
* 如果没有可用的线程存在, 则会创建一个新的线程,并将其添加到线程池中。
* 如果线程60秒没有被使用,那么会被终结掉并从线程池中移除。
* 因此,长时间保持闲置的线程池不会占用资源.
* 可以使用ThreadPoolExecutor构造方法,创建具有相似属性不同细节的线程池
* @return the newly created thread pool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
根据newCachedThreadPool源代码可以看到,newCachedThreadPool线程池中固定的核心线程数量为0,非核心线程数量不做限制,最大数量可达Integer.MAX_VALUE。线程池空闲线程超时为60秒,即空闲线程在60秒内没有新的任务执行,该线程就会释放掉。SynchronousQueue队列是一个没有存储空间的阻塞队列,这意味着只要有请求到来,就必须要有一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程出来。
newFixedThreadPool
newFixedThreadPool用于创建一个固定线程数量的线程池,并将线程资源存放在队列中循环使用。在newFixedThreadPool线程池中,若处于活动状态的线程数量大于等于核心线程池的数量,则新提交的任务将在阻塞队列中排队,直到有可用的线程资源。
* 创建一个重复使用固定线程数量的线程池来操作无界共享任务队列中任务。
* 在任何时候,最多只有nThreads数量的线程处于激活处理任务状态。
* 如果有任务在所有线程都处于活跃执行任务时提交,
* 那他们将被添加到等待队列中,直到有线程空闲可用为止.
* 如果任何线程在关闭之前的执行任务期间发生故障而终止,
* 如果须要则会有一个新的任务产生,继续执行任务。
* 线程池中的线程会一直存在,直到被显示shutdown.
* @param nThreads 线程池中线程数量
* @return 先创建的线程池
* @throws IllegalArgumentException if {@code nThreads <= 0}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
newFixedThreadPool线程池中线程数量固定,核心线程数和最大线程数均为nThreads,即线程池中只有核心线程nThreads个,没有非核心线程存在。如果提交到线程池中的任务数量大于可用的nThreads时,多余的线程会存放到LinkedBlockingQueue队列中排队等待。 LinkedBlockingQueue队列为无界阻塞队列,即不管排队的任务数量有多大,队列永远不会满,不存在任务拒绝的情况发生。
newScheduledThreadPool
newScheduledThreadPool创建一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务
* 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行
* @param corePoolSize 线程池中核心线程数量
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
newSingleThreadExecutor
newSingleThreadExecutor线程池会保证永远只有一个可用的线程,在该线程停止或发生异常时,newSingleThreadExecutor线程池会启动一个新的线程来代替该线程继续执行任务。
* 创建一个使用单个工作线程运行无界队列的执行程序
* (但是这个工作线程如果执行中失败导致终止,会有一个新的线程产生继续执行任务)
* 任务保证顺序执行,且任何时间最多只有不超过1个线程是激活状态
* 不像与它类似的newFixedThreadPool(1),返回的执行器保证不被重新配置以使用额外的线程
* @return the newly created single-threaded Executor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
newSingleThreadExecutor线程池源代码可以看到,核心线程数和最大线程数均为1,即永远只有一个线程在执行任务,因为不存在非核心线程,所以线程空闲释放参数keepAliveTime在这里是无用的。工作队列使用的是LinkedBlockingQueue,LinkedBlockingQueue为一个无界阻塞队列,其可以保证过来的任务可以被保证顺序的执行,任务不会被拒绝丢弃。
线程池测试
我们准备一个基础代码段,循环100次,每次休眠1秒钟
public static void main(String[] args) throws InterruptedException {
final long start = System.currentTimeMillis();
for (int i=0; i<100; i++) {
Thread.sleep(1000L);
System.out.println("cost " + (System.currentTimeMillis()-start) + " ms.");
我们来测试一下,正常执行这段代码的耗时
cost 100693 ms.
定义线程类
public class TestThread implements Runnable{
private int i;
public TestThread(int i) {
this.i=i;
@Override
public void run() {
System.out.printf("当前线程:%s----%d%n", Thread.currentThread().getName(), i);
try {
Thread.sleep(1000L);
}catch (Exception e){
e.printStackTrace();
newSingleThreadExecutor测试
public static void main(String[] args) throws InterruptedException {
final long start = System.currentTimeMillis();
// 定义单例线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交任务
for (int i=0;i<100;i++){
executorService.execute(new TestThread(i));
// 带任务执行完成,统计耗时
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);
System.out.println("cost "+(System.currentTimeMillis()-start) + " ms.");
单例线程池时,耗时如下
当前线程:pool-1-thread-1----0
当前线程:pool-1-thread-1----1
当前线程:pool-1-thread-1----2
......
当前线程:pool-1-thread-1----98
当前线程:pool-1-thread-1----99
cost 100942 ms.
从输出可以看到,单例线程池和未使用线程池时耗时差不多。未使用线程池时耗时100693ms,单例线程池耗时100942ms,这里耗时相近是因为他们都是只有一个线程在执行任务。
newFixedThreadPool测试
public static void main(String[] args) throws InterruptedException {
final long start = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i=0;i<100;i++){
executorService.execute(new TestThread(i));
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);
System.out.println("cost "+(System.currentTimeMillis()-start) + " ms.");
当我们设置5个线程执行任务时,耗时20225ms,比未使用线程池时的100693ms提升性能约4倍。
当前线程:pool-1-thread-1----0
当前线程:pool-1-thread-5----4
当前线程:pool-1-thread-4----3
当前线程:pool-1-thread-3----2
当前线程:pool-1-thread-2----1
当前线程:pool-1-thread-5----5
当前线程:pool-1-thread-1----9
......
当前线程:pool-1-thread-5----94
当前线程:pool-1-thread-3----95
当前线程:pool-1-thread-2----96
当前线程:pool-1-thread-1----97
当前线程:pool-1-thread-4----98
当前线程:pool-1-thread-5----99
cost 20225 ms.
当我们设置为10个线程时,测试一下
ExecutorService executorService = Executors.newFixedThreadPool(10);
耗时为10183ms,比5个线程时的耗时20225ms降低50%。
当前线程:pool-1-thread-1----0
当前线程:pool-1-thread-10----9
当前线程:pool-1-thread-9----8
当前线程:pool-1-thread-2----1
......
当前线程:pool-1-thread-10----93
当前线程:pool-1-thread-1----96
当前线程:pool-1-thread-9----99
当前线程:pool-1-thread-2----98
当前线程:pool-1-thread-8----95
当前线程:pool-1-thread-4----97
cost 10183 ms.
newCachedThreadPool测试
public static void main(String[] args) throws InterruptedException {
final long start = System.currentTimeMillis();
// 创建缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 提交任务
for (int i=0;i<100;i++){
executorService.execute(new TestThread(i));
// 待任务执行完成,统计耗时
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.DAYS);
System.out.println("cost "+(System.currentTimeMillis()-start) + " ms.");
耗时1100ms,从打印信息可以看到,缓存线程池执行任务时,给每个任务都新建了一个线程,故而100个线程执行100个任务,整体耗时降低很多。
当前线程:pool-1-thread-1----0
当前线程:pool-1-thread-51----50
当前线程:pool-1-thread-52----51
当前线程:pool-1-thread-53----52
......
当前线程:pool-1-thread-9----8
当前线程:pool-1-thread-8----7
当前线程:pool-1-thread-7----6
当前线程:pool-1-thread-6----5
当前线程:pool-1-thread-4----3
当前线程:pool-1-thread-3----2
当前线程:pool-1-thread-2----1
cost 1100 ms.
Executors自有线程池缺陷
看个测试示例
将NewSingleThreadExecutorMain测试方法中,任务数量修改为1000000,然后将虚拟机参数调小为 -Xms8m -Xmx8m,执行
// 提交任务
for (int i=0;i<1000000;i++){
executorService.execute(new TestThread(i));
执行过程会报OOM异常,代码中17行为executorService.execute(new TestThread(i)); 即在向任务队列中添加任务时报出了OutOfMemoryError
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
at java.util.concurrent.Executors$DelegatedExecutorService.execute(Executors.java:668)
at com.thread.pool.NewSingleThreadExecutorMain.main(NewSingleThreadExecutorMain.java:17)
Exception in thread "pool-1-thread-1" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1043)
at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
at sun.util.resources.ParallelListResourceBundle.loadLookupTablesIfNecessary(ParallelListResourceBundle.java:169)
at sun.util.resources.ParallelListResourceBundle.handleKeySet(ParallelListResourceBundle.java:134)
......
at java.text.DecimalFormatSymbols.<init>(DecimalFormatSymbols.java:113)
at sun.util.locale.provider.DecimalFormatSymbolsProviderImpl.getInstance(DecimalFormatSymbolsProviderImpl.java:85)
at java.text.DecimalFormatSymbols.getInstance(DecimalFormatSymbols.java:180)
......
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-2" java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: GC overhead limit exceeded
为何出现这个情况,上面在说newSingleThreadExecutor单例线程池时,单例线程池会使用无界阻塞队列LinkedBlockingQueue来存放待线程处理的任务,由于LinkedBlockingQueue的无界属性,它不会拒绝任务,当任务数量达到一定量时,可能耗尽虚拟机内存,从而引发OOM错误。
再来比对各个线程池情况
newSingleThreadExecutor和newFixedThreadPool线程池都使用无界阻塞队列LinkedBlockingQueue来存放工作队列,他们都可能会因为工作队列中任务数量过大而产生内存溢出问题。
那newCachedThreadPool线程池会不会出现内存溢出呢,看一个示例,创建100万个任务给缓存线程池让其执行,指定虚拟机参数 -Xms8m -Xmx8m
// 创建缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 提交1000000个任务给缓存线程池
for (int i=0;i<1000000;i++){
executorService.execute(new TestThread(i));
执行过程中会出现如下错误,即出现OutOfMemoryError
当前线程:pool-1-thread-733----16224
Exception in thread "pool-1-thread-8146" 当前线程:pool-1-thread-8148----16227
当前线程:pool-1-thread-724----5557
当前线程:pool-1-thread-8151----16230
当前线程:pool-1-thread-735----5556
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.valueOf(Integer.java:832)
at com.thread.pool.TestThread.run(TestThread.java:13)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
当前线程:pool-1-thread-5271----5387
当前线程:pool-1-thread-725----5554
当前线程:pool-1-thread-736----16234
当前线程:pool-1-thread-723----Exception in thread "pool-1-thread-726" java.lang.OutOfMemoryError: GC overhead limit exceeded
当前线程: at java.util.regex.Matcher.<init>(Matcher.java:225)
at java.util.regex.Pattern.matcher(Pattern.java:1093)
pool-1-thread-722----5552
at java.util.Formatter.parse(Formatter.java:2547)
at java.util.Formatter.format(Formatter.java:2501)
当前线程:pool-1-thread-721----5551
当前线程:pool-1-thread-718---- at java.io.PrintStream.format(PrintStream.java:970)5550
当前线程:pool-1-thread-720----5549
at java.io.PrintStream.printf(PrintStream.java:871)
当前线程:pool-1-thread-719----5548
at com.thread.pool.TestThread.run(TestThread.java:13)
给个截图看着清晰一些
这里的错误,主要看GC overhead limit exceeded,出现该错误是内存频繁GC的时候,只能回收少量内存,大量内存无法被回收从而导致内存溢出错误。
这里的主要问题是,缓存线程池newCachedThreadPool中使用无容量阻塞队列SynchronousQueue,导致来了任务后,如果没有空闲可用线程使用,则会创建一个新线程出来,当线程池内存活的线程数量太大时,则可能因存活的线程而耗尽java虚拟机内存,导致OutOfMemoryError。
关于使用Java中默认的线程池,阿里的编码规范中有明确要求,严禁使用Java默认的线程池
经上面示例可以看到,Executors中提供的默认线程池,在使用不好的情况下,都可能会产生OOM异常。所以,实际使用中,默认的线程池一定要遵循各自适合的场景使用,或者,使用自定义线程池。
Java定义了Executor接口并在该接口中定义了execute( )用于执行一个线程任务,然后通过ExecutorService实现Executor接口并执行具体的线程操作。ExecutorService接口有多个实现类可用于创建不同的线程池。newCachedThreadPoolnewCachedThreadPool用于创建衣蛾缓存线程池。之所以叫缓存线程池,是因为它在创建新线程时,如果有可重用的线程,则重用他们,否则重新创建一个新的线程并将其添加到线程池中。对于执行时间很短的任务惹眼,new
newFixedThreadPool:是一个定长线程池,也就是核心线程数和最大线程数相等,阻塞队列采用LinkedBlockingQueue,是一个无界队列
使用场景:适用于CPU密集型的任务,已有的线程数量已经可以充分的利用CPU的性能,不需要再去创建额外的线程
缺点:当大量的任务提交过来时可能会造成一个任务的大量堆积,从而导致阻塞队列的元素过多造成占用一个大量的内存
newCachedThreadPool:阻塞队列采用的是SynchronizedQueue,是一个容量为零的队列,也就是说每当一个生产线程
Executors.newScheduledThreadPool()
在jdk8中,CompletableFuture腾空出世,它简化了异步任务的写法,提供了很多异步任务的计算方式。
言归正传,现在生产上面出现的问题是,在
Java SDK并发包通过Lock和Condition两个接口来实现管程(管程——并发编程的万能钥匙),其中Lock用于解决互斥问题,Condition用于解决同步问题。
再造管程的理由
既然Java里已经存在管程的实现synchronized并且做了许多优化,为什么还需要在并发包里开发Lock和Condition。原因是synchronized申请资源的时候,如果申请不到,线程直接进入阻塞状态,而线程进入阻塞状态,啥也干不了,也释放不了线程已经占有的资源。
我们希望的是,对于“不可抢占”这个条件,占用部分
一起学JAVA之【基础篇】4种默认线程池介绍
默认线程池创建方式
java.util.concurrent 提供了一个创建线程池的工具类Executors,里面有四种常用的线程池创建方法
public class DemoThreadPool{
public static void main(String[] args){
//创建一个核心线程数和最大线程数相同的线程池
ExecutorService executorService = Executors.newFi
创建一个固定线程数的线程池,核心线程数和最大线程数固定相等。
keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉,不过因为最多只有nThreads个线程,且corePoolSize和maximunPoolSize值一致,所以这个值无法发挥作用。
阻塞队列采用了LinkedBlockingQueue,它是一个无界队列,由于阻塞队列是一个无界队列,因此永
一、线程池
线程池可以看做是线程的集合。它的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后 启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕, 再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。
线程复用:
每一个 Thread 的类都有一个 start 方法。 当调用 start 启动线程时 Java 虚拟机会调用该类的 run 方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run(
线程中join(强制执行):当前线程执行完毕之后,才会执行后面程序,其他线程阻塞;
public class ThreadJoin implements Runnable{
@Override
public void run() {
for (int i = 1; i < 1000; i++) {
System.out.println("Join..."+i+"运行中");
public stat
1、Java内置线程池:ThreadPoolExecutor
2、通过Executor工厂类中的静态方法获取线程池对象
第一种、通过newCachedThreadPool获取线程池对象
第二种、通过newFixedThreadPool获取线程池对象
第三种、通过newSingleThreadExecutor获取线程池对象
关闭线程方法:shutdown和shutdownNow的区别
三种创建线程池的
在Java中,使用线程池可以方便地创建多个线程。线程池可以维护一组线程,并且可以让这些线程重复利用,减少了线程的创建和销毁的开销,提高了程序的性能。以下是创建线程池的代码示例:
// 创建一个固定大小的线程池,大小为10
ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交任务给线程池执行
executor.submit(new Runnable() {
@Override
public void run() {
// 线程执行的任务内容
// 关闭线程池
executor.shutdown();
使用线程池时,需要注意线程池的大小设置,如果线程池大小设置过大,会浪费系统资源;如果线程池大小设置过小,会导致任务排队等待执行,影响程序的响应速度。此外,还需要注意线程安全问题,线程池中的线程可能会同时访问共享资源,需要使用锁或其他并发控制方式来保证线程安全。
Consider defining a bean of type ‘org.springframework.http.codec.ServerCodecConfigurer‘ in your conf
Kangrant:
Redis中字符串(string)与散列表(hash)比较
titer1: