开启大量线程会有什么问题,如何优化?
这道题想考察什么?
- 是否了解线程开启的方式?
- 开启大量线程会引起什么问题?为什么?怎么优化?
考察的知识点
- 线程的开启方式
- 开启大量线程的问题
- 线程池
考生应该如何回答
1、首先,关于如何开启一个线程,大多数人可能都会说3种,Thread、Runnable、Callable嘛!但事实却不是这样的。看JDK里怎么说的。
/*** ...* There are two ways to create a new thread of execution. One is to* declare a class to be a subclass of <code>Thread</code>. * The other way to create a thread is to declare a class that* implements the <code>Runnable</code> interface.* ....*/
public class Thread implements Runnable{}
Thread源码的类描述中有这样一段,翻译一下,只有两种方法去创建一个执行线程,一种是声明一个Thread的子类,另一种是创建一个类去实现Runnable接口。惊不惊讶,并没有提到Callable。
继承Thread类
public class ThreadUnitTest {@Testpublic void testThread() {//创建MyThread实例MyThread myThread = new MyThread();//调用线程start的方法,进入可执行状态myThread.start();}//继承Thread类,重写内部run方法static class MyThread extends Thread {@Overridepublic void run() {System.out.println("test MyThread run");}}
}
实现Runnable接口
public class ThreadUnitTest {@Testpublic void testRunnable() {//创建MyRunnable实例,这其实只是一个任务,并不是线程MyRunnable myRunnable = new MyRunnable();//交给线程去执行new Thread(myRunnable).start();}//实现Runnable接口,并实现内部run方法static class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("test MyRunnable run");}}
}
Callable
public class ThreadUnitTest {@Testpublic void testCallable() {//创建MyCallable实例,需要与FutureTask结合使用MyCallable myCallable = new MyCallable();//创建FutureTask,与Runnable一样,也只能算是个任务FutureTask<String> futureTask = new FutureTask<>(myCallable);//交给线程去执行new Thread(futureTask).start();try {//get方法获取任务返回值,该方法是阻塞的String result = futureTask.get();System.out.println(result);} catch (ExecutionException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}//实现Callable接口,并实现call方法,不同之处是该方法有返回值static class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {Thread.sleep(10000);return "test MyCallable run";}}
}
Callable的方式必须与FutureTask结合使用,我们看看FutureTask的继承关系
//FutureTask实现了RunnableFuture接口
public class FutureTask<V> implements RunnableFuture<V> {}//RunnableFuture接口继承Runnable和Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> {void run();
}
真相大白了,其实实现Callback接口创建线程的方式,归根到底就是Runnable方式,只不过它是在Runnable的基础上又增加了一些能力,例如取消任务执行等。
线程开启后CPU调度会发生什么?
计算机的世界里,CPU会分为若干时间片,通过各种算法分配时间片来执行任务,有耳熟能详时间片轮转调度算法、短进程优先算法、优先级算法等。当一个任务的时间片用完,就会切换到另一个任务。在切换之前会保存上一个任务的状态,当下次再切换到该任务,就会加载这个状态, 这就是所谓的线程的上下文切换。
很明显,上下文的切换是有开销的,包括很多方面,操作系统保存和恢复上下文的开销、线程调度器调度线程的开销和高速缓存重新加载的开销等。
经过上面两个理论基础的回顾,开启大量线程引起的问题,总结起来,就两个字——开销。
- 消耗时间 线程的创建和销毁都需要时间,当数量太大的时候,会影响效率。
- 消耗内存 创建更多的线程会消耗更多的内存,这是毋庸置疑的。线程频繁创建与销毁,还有可能引起内存抖动,频繁触发GC,最直接的表现就是卡顿。长而久之,内存资源占用过多或者内存碎片过多,系统甚至会出现OOM。
- 消耗CPU 在操作系统中,CPU都是遵循时间片轮转机制进行处理任务,线程数过多,必然会引起CPU频繁的进行线程上下文切换。这个代价是昂贵的,某些场景下甚至超过任务本身的消耗。
针对上面提及到的问题,我们自然需要进行优化。线程的本质是为了执行任务,在计算机的世界里,任务分大致分为两类,CPU密集型任务和IO密集型任务。
CPU密集型任务
,比如公式计算、资源解码等。这类任务要进行大量的计算,全都依赖CPU的运算能力,持久消耗CPU资源。所以针对这类任务,其实不应该开启大量线程。因为线程越多,花在线程切换的时间就越多,CPU执行效率就越低,一般CPU密集型任务同时进行的数量等于CPU的核心数,最多再加个1。IO密集型任务
,比如网络读写、文件读写等。这类任务不需要消耗太多的CPU资源,绝大部分时间是在IO操作上。所以针对这类任务,可以开启大量线程去提高CPU的执行效率,一般IO密集型任务同时进行的数量等于CPU的核心数的两倍。
另外,在无法避免,必须要开启大量线程的情况下,我们也可以使用线程池代替直接创建线程的做法进行优化。线程池的基本作用就是复用已有的线程,从而减少线程的创建,降低开销。
在Java中,线程池的使用还是非常方便的,JDK中提供了现成的ThreadPoolExecutor
类,我们只需要按照自己的需求进行相应的参数配置即可,这里提供一个示例。
/*** 线程池使用*/
public class ThreadPoolService {/*** 线程池变量*/private ThreadPoolExecutor mThreadPoolExecutor;private static volatile ThreadPoolService sInstance = null;/*** 线程池中的核心线程数,默认情况下,核心线程一直存活在线程池中,即便他们在线程池中处于闲置状态。* 除非我们将ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true的时候,这时候处于闲置的核心 * 线程在等待新任务到来时会有超时策略,这个超时时间由keepAliveTime来指定。一旦超过所设置的超时时间,闲 * 置的核心线程就会被终止。* CPU密集型任务 N+1 IO密集型任务 2*N*/private final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() + 1;/*** 线程池中所容纳的最大线程数,如果活动的线程达到这个数值以后,后续的新任务将会被阻塞。包含核心线程数+非* * 核心线程数。*/private final int MAXIMUM_POOL_SIZE = Math.max(CORE_POOL_SIZE, 10);/*** 非核心线程闲置时的超时时长,对于非核心线程,闲置时间超过这个时间,非核心线程就会被回收。* 只有对ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true的时候,这个超时时间才会对核心线 * 程产生效果。*/private final long KEEP_ALIVE_TIME = 2;/*** 用于指定keepAliveTime参数的时间单位。*/private final TimeUnit UNIT = TimeUnit.SECONDS;/*** 线程池中保存等待执行的任务的阻塞队列* ArrayBlockingQueue 基于数组实现的有界的阻塞队列* LinkedBlockingQueue 基于链表实现的阻塞队列* SynchronousQueue 内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间* PriorityBlockingQueue 具有优先级的无限阻塞队列。*/private final BlockingQueue<Runnable> WORK_QUEUE = new LinkedBlockingDeque<>();/*** 线程工厂,为线程池提供新线程的创建。ThreadFactory是一个接口,里面只有一个newThread方法。 默认为DefaultThreadFactory类。*/private final ThreadFactory THREAD_FACTORY = Executors.defaultThreadFactory();/*** 拒绝策略,当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者是无法成功执行任务,这时候 * ThreadPoolExecutor会调用RejectedExecutionHandler中的rejectedExecution方法。* CallerRunsPolicy 只用调用者所在线程来运行任务。* AbortPolicy 直接抛出RejectedExecutionException异常。* DiscardPolicy 丢弃掉该任务,不进行处理。* DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务。*/private final RejectedExecutionHandler REJECTED_HANDLER = new ThreadPoolExecutor.AbortPolicy();private ThreadPoolService() {}/*** 单例* @return*/public static ThreadPoolService getInstance() {if (sInstance == null) {synchronized (ThreadPoolService.class) {if (sInstance == null) {sInstance = new ThreadPoolService();sInstance.initThreadPool();}}}return sInstance;}/*** 初始化线程池*/private void initThreadPool() {try {mThreadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE_TIME,UNIT,WORK_QUEUE,THREAD_FACTORY,REJECTED_HANDLER);} catch (Exception e) {LogUtil.printStackTrace(e);}}/*** 向线程池提交任务,无返回值** @param runnable*/public void post(Runnable runnable) {mThreadPoolExecutor.execute(runnable);}/*** 向线程池提交任务,有返回值** @param callable*/public <T> Future<T> post(Callable<T> callable) {RunnableFuture<T> task = new FutureTask<T>(callable);mThreadPoolExecutor.execute(task);return task;}
}
开启大量线程会有什么问题,如何优化?相关推荐
- python获取电脑几核几线程_python编程测试电脑开启最大线程数实例代码
本文实例代码主要实现python编程测试电脑开启最大线程数,具体实现代码如下. #!/usr/bin/env python #coding=gbk import threading import ti ...
- python关闭线程daemon_python中threading开启关闭线程操作
在python中启动和关闭线程: 首先导入threading import threading 然后定义一个方法 def serial_read(): ... ... 然后定义线程,target指向要 ...
- 第三节:ThreadPool的线程开启、线程等待、线程池的设置、定时功能
一. ThreadPool简介 ThreadPool简介:ThreadPool是一个线程池,当你需要开启n个线程时候,只需把这个指令抛给线程池,它将自动分配线程进行处理,它诞生于.Net 2.0时代. ...
- python测试电脑性能的代码_python编程测试电脑开启最大线程数实例代码
本文实例代码主要实现python编程测试电脑开启最大线程数,具体实现代码如下. #!/usr/bin/env python #coding=gbk import threading import ti ...
- main线程 子线程 顺序_在主线程main中开启子线程thread示例
主线程中开启子线程代码实例: 创建CancleThread类 继承 Thread,对run()方法进行重写代码实例: class CancleThread extends Thread { priva ...
- 【转载】JVM能够开启多少线程
JVM能够开启多少线程 最近在看<<Java并发编程实战>>一书过程中,在Task Execution一节中讲到,针对每个任务都启动一个线程来处理,尤其在大量创建线程的场景下, ...
- JVM能够开启多少线程
最近在看<<Java并发编程实战>>一书过程中,在Task Execution一节中讲到,针对每个任务都启动一个线程来处理,尤其在大量创建线程的场景下,会给工程实践带来很多问题 ...
- 多线程总结:1.线程的创建于开启 2.线程的状态 3.线程安全
多线程 三高: 高可用 高性能 高并发 thread 类 线程是程序中执行的线程. Java虚拟机允许应用程序同时运行多个执行线程. 多线程: 多任务执行 ...
- php显示时间秒针走,Android_TextView显示系统时间(时钟功能带秒针变化,我们开启一个线程,线程每隔 - phpStudy...
TextView显示系统时间(时钟功能带秒针变化 我们开启一个线程,线程每隔一秒发送一次消息,我们在消息中更新TextView上显示的时间就ok了. 首先我们在布局文件中放一个TextView用来显示 ...
- C++开启后台线程监听控制台输入实现按任意键退出
//通过开启后台线程监听控制台输入来实现按任意键退出 //也可以自行修改成其他键 vector<thread> threads; threads.push_back(thread([]() ...
最新文章
- 在 react 里使用 antd
- 一文了解什么是指数族分布
- 全国大学生智能车智能汽车竞赛车模检查规范
- 常考数据结构与算法-morris遍历
- jvm监控工具(排查内存溢出和内存泄漏)
- 中缀表达式转换为前缀或后缀表达式的手工做法
- c++代码好玩_一行Python代码能干嘛?快来看看吧!有表白利器哦
- 对 飞秋 程序设计的一些新想法
- Oracle数据库执行Sql脚本
- 数据结构题集c语言版题目与答案,数据结构题集(C语言版)答案 - 严蔚敏编著...
- 华为云modelarts平台SDK的调用
- Linux安装/升级/卸载pip3
- 刷armbian后必要的安装和配置
- 2018年世界计算机超算大赛,在世界大学生超级计算机竞赛(ASC18)总决赛中 青海大学超算团队成功获得ASC竞赛全球一等奖...
- ACM——01——1003: 【入门】求任意三位数各个数位上数字的和 【运算符】
- 最近程序员频繁被抓,如何避免面向监狱编程!?
- HTML中表格写法,在HTML代码里面表格的写法以及表格的特性
- centos7搭建http代理ip TinyProxy 及验证是否有效(python)
- 社交网站需要多大的服务器空间,社交app选多大云服务器
- 罗密哦与朱丽叶----this指针、函数封装