查看原文
其他

Java提供的几种线程池

点击上方 "程序员小乐"关注公众号, 星标或置顶一起成长

每天早上8点20分, 第一时间与你相约

每日英文

Don't forget what to do and where to go you have promised yourself,no matter how difficult or far it is.

别忘了答应自己要做的事情,别忘了答应自己要去的地方,无论有多难,有多远。


每日掏心话

你若光明,这世界就不会黑暗。你若心怀希望,这世界就不会彻底绝望。你如不屈服,这世界又能把你怎样。


来自:天天不是小可爱 | 责编:乐乐

链接www.cnblogs.com/QuixoteY/p/11243997.html

程序员小乐(ID:study_tech)第 638 次推文   图片来自网络


往日回顾:VS Code为什么能这么牛?



   正文   


线程池,顾名思义,放线程的池子嘛,这个池子可以存放多少线程取决于你自己采用什么样的线程池,你的硬件资源,以及并发线程的数量。JDK提供了下面的四种线程池:

固定线程数的线程池

1、最简单的

在Java中创建一个线程池,这很简单,只需要两行代码。

ExecutorService executor = Executor.newFixedTreadPool(6);//固定线程是6//线程一般设置成processor核心数的倍数,因为我这台机器是6核的,所以设成6。这也是充分利用硬件嘛
//执行线程任务executor.execute(new Runnable(){@Overridepublic void run(){ //do nothing }}) executor.shutdown();

Executor是Java并发包中提供的,用来创造不同类型的线程池。

Attention

但是在多人合作或者是一些部署上线的项目里,是不允许去使用这种方法的,因为它是有性能隐患的。

Executors在创建线程池的时候,用的是new LinkedBlockingQueue(),它这个队列本身是无边界的,但是线程是固定数量的。这就意味着,在程序运行的过程中,最多会有N个线程在处于活动状态。每次有新的任务来就会等待,直到有线程处于空闲状态。所有的线程都会处于线程池里里面,直到shutdown()的执行。

它的问题就在于来者不拒,只要有任务来,你就进队列等着。在入队列和出队列用的并不是同一个lock,在多processor的机器上,是可以做到真正意义上的并行的。拿经典的生产者和消费者来举例子,在同一个时间点,有的在消费,有的在生产。

这种线程池不会销毁线程,不会拒绝任务,固定线程数。所以如果不停的加入任务,会导致很糟糕的内存占用,老年代可能会被占满。

1、稍复杂的(可以延时执行,也可以执行带返回值的任务)

public static void main(String[] args) throws InterruptedException, ExecutionException { TestThread testThread = new TestThread(); System.out.println(testThread.processors);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(6); FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { return Thread.currentThread().getName(); } }); scheduledExecutorService.submit(futureTask);
//获取返回值 String result = futureTask.get(); System.out.println("result :"+result);
//执行延时任务 scheduledExecutorService.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+": bomb!"); } },3L,TimeUnit.SECONDS); }

Output:

result :pool-1-thread-1
pool-1-thread-1: bomb!

缓存的线程池

核心池大小为0,线程池最大线程数目为最大整型,这意味着所有的任务一提交就会wait。当线程池中的线程有60s没有执行任务就会被Kill,阻塞队列为SynchronousQueue。SynchronousQueue的take操作需要put操作等待,put操作需要take操作等待,否则会阻塞(线程池的阻塞队列不能存储,所以当目前线程处理忙碌状态时,会开辟新的线程来处理请求**),线程进入wait set。

总结一下这是一个可以无限扩大的线程池;适合处理执行时间比较小的任务;线程空闲时间超过60s就会被Kill,所以长时间处于空闲状态的时候,这种线程池几乎不占用资源,因为它压根没有线程在里面;阻塞队列没有存储空间,只要请求到来,就必须找到一条空闲线程去处理这个请求,找不到则在线程池新开辟一条线程。

如果主线程提交任务的速度远远大于CachedThreadPool的处理速度,则CachedThreadPool会不断地创建新线程来执行任务,这样有可能会导致系统耗尽CPU和内存资源,所以在使用该线程池时,要注意控制并发的任务数。如果是一个不断增长的任务需求,很容易就会到性能瓶颈,它会不停的创建新的线程。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { cachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } cachedThreadPool.shutdown();

Output:

pool-1-thread-2
pool-1-thread-6
pool-1-thread-1
pool-1-thread-7
pool-1-thread-8
pool-1-thread-3
pool-1-thread-5
pool-1-thread-9
pool-1-thread-4
pool-1-thread-10

单个线程的线程池

SingleThreadExecutor 是使用单个worker线程的Executor。只有一种情况会有新的线程加入线程池,那就是原有的线程运行时有抛出异常,这时就会有创建的新的线程来替代它的工作。

拿生产者消费者模型来说的话,这就是一个单一消费者的模型

(ps.一般可以用来做一些日志记录

public static void main(String[] args) { // 永远是一条线程 ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int j = i; singleThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + j); } }); } singleThreadPool.shutdown(); }

Output:

pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-1:2
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-1:6
pool-1-thread-1:7
pool-1-thread-1:8
pool-1-thread-1:9


欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。


欢迎各位读者加入程序员小乐技术群,在公众号后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

GitHub 标星3.5W+,超实用技术面试手册,从工作申请、面试考题再到优势谈判

漫画:一位“坑人”的编程大师

Redis基础都不会,好意思出去面试?

理解MySQL锁和事务,看这篇如何?

掌握 SpringMVC 运行原理,看这篇就对了!

关注「程序员小乐」,收看更多精彩内容
嘿,你在看吗?

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存