JUC的Executors类的4种线程池及相关面试点

目录

(Executors类的)线程池有几种?

1.newFixedThreadPool()

2.newSingleThreadExecutor

3.newCachedThreadPool

4.newScheduledThreadPool

说一下常见的几种阻塞队列?

线程池都有哪些状态?

线程池中 submit() 和 execute() 方法有什么区别?


package java.util.concurrent.Executors;

Executors是juc包下的类,Executor是juc包下的接口

Executors类中提供了四种线程池
        分别为
        newCachedThreadPool
        newFixedThreadPool
        newScheduledThreadPool
        newSingleThreadExecutor
这四种线程池本地都是通过用不同的参数去 new ThreadPoolExecutor 实现的,也就是说想要理解好这四种线程池的原理以及应用场景,还是需要去了解ThreadPoolExecutor 。
阿里开发手册上不建议使用Executors类提供的四种线程池,会出现内存溢出的错误(OOMOut Of Memory

(Executors类的)线程池有几种?

1.newFixedThreadPool()

由于使用了LinkedBlockingQueue,并且最大线程数和核心线程数设置相同的值,当corePoolSize满了之后就加入到LinkedBlockingQueue队列中。每当某个线程执行完成之后就从LinkedBlockingQueue队列中取一个。所以这个是创建固定大小的线程池

  • 特点:创建一个定长线程池,最大线程数和核心线程数相同,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。

  • 缺点:线程数量是固定的,但是阻塞队列是无界队列。如果有很多请求积压,阻塞队列越来越长,容易导致OOM(超出内存空间)

  • 总结:请求的挤压一定要和分配的线程池大小匹配,定线程池的大小最好根据系统资源进行设置。Runtime.getRuntime().availableProcessors()方法是查看电脑CPU核心数量

  • 代码示例:

package com.lijie;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestNewFixedThreadPool {
    public static void main(String[] args) {
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newFixedThreadPool.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ",i==" + temp);
                }
            });
        }
    }
}

2.newSingleThreadExecutor

创建线程池核心线程数和最大线程数均为1的具有缓冲队列的线程池,缓冲队列使用了LinkedBlockingQueue。每次使用的都是同一个线程,作用是:保证任务按照顺序执行。

  • 特点:创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,他必须保证前一项任务执行完毕才能执行后一项,保证了队列中所有任务按照指定的顺序执行

  • 缺点:缺点的话,很明显,他是单线程的,高并发业务下有点无力

  • 总结:保证所有任务按照指定顺序执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它

  • 代码示例:

package com.lijie;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestNewSingleThreadExecutor {
    public static void main(String[] args) {
        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int index = i;
            newSingleThreadExecutor.execute(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " index:" + index);
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                    }
                }
            });
        }
    }
}

3.newCachedThreadPool

创建可缓冲的线程池核心线程数corePoolSize为0最大线程数maxumumPoolSize为Integer.MAX_VALUE由于corePoolSize为0所以任务会放入SynchronousQueue队列(SynchronousQueue队列每一个put必须等待一个take操作,否则不能继续添加元素)中,SynchronousQueue只能存放大小为1,所以会立刻新起线程。如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为 1 分钟),则该工作线程将自动终止如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的长度作任何限制,容易造成堆外内存溢出

  • 特点:newCachedThreadPool创建一个可缓冲的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的长度作任何限制

  • 缺点:他虽然可以无线的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问题的点,就可以去重写一个方法限制一下这个最大值。

  • 总结:线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

  • 代码示例:

package com.lijie;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestNewCachedThreadPool {
    public static void main(String[] args) {
        // 创建无限大小线程池,由jvm自动回收
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newCachedThreadPool.execute(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                    }
                    System.out.println(Thread.currentThread().getName() + ",i==" + temp);
                }
            });
        }
    }
}

4.newScheduledThreadPool

支持定时及周期性任务执行的线程池,使用的阻塞队列是DelayQueue(支持延时获取元素的阻塞队列,即可以指定多久才能从队列中获取当前元素

由以上四种线程池,可以看出前三种是直接new的ThreadPoolExecutor创建出来的。而最后一种是继承了ThreadPoolExecutor,添加了一些方法。 

  • 特点:创建一个固定长度的线程池,而且支持定时的以及周期性的任务执行,类似于Timer(Timer是Java的一个定时器类)

  • 缺点:由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。

  • 代码示例:

package com.lijie;
 
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
 
public class TestNewScheduledThreadPool {
    public static void main(String[] args) {
        //定义线程池大小为3
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println("i:" + temp);
                }
            }, 3, TimeUnit.SECONDS);//这里表示延迟3秒执行。
        }
    }
}

说一下常见的几种阻塞队列?

常用的是这四种:
LinkedBlockingQueue一个由链表结构组成的阻塞队列,此队列按照先进先出的原则对元素进行排序。
SynchronousQueue不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素,并且他支持公平访问队列。

public class SynchronousQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable

DelayQueue支持延时获取元素的阻塞队列,即可以指定多久才能从队列中获取当前元素。

ArrayBlockingQueue 数组结构组成的阻塞队列。此队列默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(公平,即先阻塞的先插入)

不常用:

PriorityBlockingQueue支持优先级的阻塞队列。

LinkedTransferQueue由链表结构组成的阻塞队列,LinkedTransferQueue是线程安全的,且性能高于BlockingQueue。

LinkedBlockingDeque链表结构的双向阻塞队列,优势在于多线程入队时减少一半的竞争

Queue

Queue 遵从先进先出原则,使用时尽量避免 add()和 remove()方法,而是使用offer()来添加元素,使用 poll()来移除元素,它的优点是可以通过返回值来判断是否成功,LinkedList 实现了 Queue 接口Queue 通常不允许插入 null 元素

newFixedThreadPool和newSingleThreadExecutor使用LinkedBlockingQueue阻塞队列。newCachedThreadPool使用SynchronousQueue阻塞队列。

newScheduledThreadPool使用DelayQueue阻塞队列。

线程池都有哪些状态?

  • RUNNING这是最正常的状态,队列可以接受新的任务,并处理队列中的任务。
  • SHUTDOWN:不接受新的任务提交,队列不接受新的任务,但会继续处理已在等待队列中的任务
  • STOP:不接受新的任务提交,队列不接受新的任务,且不再处理已在等待队列中的任务,并且中断正在执行任务的线程
  • TIDYING:当线程池中的所有任务都执行完毕,包括队列中的任务,且活动线程数workCount 为零时,线程池会进入这个状态。线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()
  • TERMINATEDterminated()方法结束后,线程池的状态就会变成这个

线程池中 submit() 和 execute() 方法有什么区别?

  • 相同点:
    • 相同点就是都可以开启线程执行池中的任务。
  • 不同点:
    • 接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。
    • 返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()不行。
    • 异常处理:submit()方便Exception处理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值