三、多线程
38.创建线程有哪几种方式?
继承成Thread 类,实现Runnable 接口,通过Callable和Future创建线程,ExecutorService Callable<Class> Future 有返回值线程
39.说一下 runnable 和 callable 有什么区别?
40.线程有哪些状态?
41.sleep() 和 wait() 有什么区别?
1. 对于sleep()方法,我们首先要知道该方法是属于Thread 类中的。而wait()方法,则是属于Object 类中的。
2. sleep()方法导致了程序暂停执行指定的时间,让出cpu 给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
3. 在调用sleep()方法的过程中,线程不会释放对象锁。
4. 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
42.notify()和 notifyAll()有什么区别?
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象
上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调
用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继
续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
43.线程的 run()和 start()有什么区别?
1. start()方法来启动线程,真正实现了多线程运行。这时无需等待run 方法体代码执行完毕,可以直接继续执行下面的代码。
2. 通过调用Thread 类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
3. 方法run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后CPU 再调度其它线程。
44.创建线程池有哪几种方式?
45.线程池都有哪些状态?
46.线程池中 submit()和 execute()方法有什么区别?
47.在 java 程序中怎么保证多线程的运行安全?
48.多线程锁的升级原理是什么?
49.什么是死锁?
50.怎么防止死锁?
57.线程池的核心参数设置(道富银行)
1.CorePoolSize:核心线程数
核心线程会一直存活,即使没有任务执行
当线程数小于核心线程数时,即使线程有空闲,线程池也会优先创建新的线程处理
设置allowCoreThredTime=true(默认false)时,核心线程会超时时关闭
Io密集型cpu性能相当于硬盘内存要好很多
CorepoolSize=cpu核数*2;
cpu密集型:系统硬盘内存性能相当月cpu要好很多
corePoolSize=cpu核数+1
2.maximumPoolSize:最大线程数
当线程数>=corePooSize,且任务队列已满时,线程池活创建新的线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
3.KeepAliveTIme:线程空闲时间
当线程空闲时间达到keepLiveTIme时,线程会制动退出,直到线程数量=corePoolSize。
如果allowCoreThreadTimeOut=true,则会到线程数量=0
4.queueCapacity:任务队列容量(阻塞队列)
当核心数线程数量达到最大时,先的任务会放在队列中排队等待执行
5.allowCoreThreadTimeout:允许核心线程超时
6.RejectedExectionHandler:任务拒绝处理器
两种情况会拒绝处理任务
当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务
当线程池被调用shutdown()后,会等线程池里的任务执行完毕在shutdown,如果在调用shutdown()和线程池真正的shutdown直接提交任务会拒绝新任务
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常。
ThreadPoolExecutor 采用了策略的设计模式来处理拒绝任务的几种场景。
这几种策略模式都实现了RejectedExecutionHandler 接口。
AbortPolicy 丢弃任务,抛运行时异常。
CallerRunsPolicy 执行任务。
DiscardPolicy 忽视,什么都不会发生。
DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
三、如何设置参数
默认值:
corePoolSize = 1
maxPoolSize = Integer.MAX_VALUE
queueCapacity = Integer.MAX_VALUE
keepAliveTime = 60s
allowCoreThreadTimeout = false
rejectedExecutionHandler = AbortPolicy()
如何来设置呢?
需要根据几个值来决定
tasks :每秒的任务数,假设为500~1000
taskcost:每个任务花费时间,假设为0.1s
responsetime:系统允许容忍的最大响应时间,假设为1s
做几个计算
corePoolSize = 每秒需要多少个线程处理?
threadcount = tasks/(1/taskcost) = tasks*taskcout = (500 ~ 1000)*0.1 = 50~100 个线程。
corePoolSize设置应该大于50。
根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可。
queueCapacity = (coreSizePool/taskcost)*responsetime
计算可得 queueCapacity = 80/0.1*1 = 800。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行。
切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
maxPoolSize 最大线程数在生产环境上我们往往设置成corePoolSize一样,这样可以减少在处理过程中创建线程的开销。
rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理。
keepAliveTime和allowCoreThreadTimeout采用默认通常能满足。
以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu load已经满了,则需要通过升级硬件和优化代码,降低taskcost来处理。
以下是我自己的的线程池配置:
@Configuration
public class ConcurrentThreadGlobalConfig {
@Bean
public ThreadPoolTaskExecutor defaultThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数目
executor.setCorePoolSize(65);
//指定最大线程数
executor.setMaxPoolSize(65);
//队列中最大的数目
executor.setQueueCapacity(650);
//线程名称前缀
executor.setThreadNamePrefix("DefaultThreadPool_");
//rejection-policy:当pool已经达到max size的时候,如何处理新任务
//CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
//对拒绝task的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//线程空闲后的最大存活时间
executor.setKeepAliveSeconds(60);
//加载
executor.initialize();
return executor;
}
}
四、线程池队列的选择
workQueue - 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交。从参数中可以看到,此队列仅保存实现Runnable接口的任务。
这里再重复一下新任务进入时线程池的执行策略:
当正在运行的线程小于corePoolSize,线程池会创建新的线程。
当大于corePoolSize而任务队列未满时,就会将整个任务塞入队列。
当大于corePoolSize而且任务队列满时,并且小于maximumPoolSize时,就会创建新额线程执行任务。
当大于maximumPoolSize时,会根据handler策略处理线程。
1、无界队列
队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而博主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。
当然这种队列,maximumPoolSize 的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
2、有界队列
当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
3、同步移交队列
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
线程池中各个参数如何合理设置_riemann_的博客-CSDN博客_线程池参数设置原则
58.关于Executors和ThreadPoolExecutor具体的区别是什么呢?
线程池不使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。
综上所述,虽然Executors的初衷是为了让我们更方便地、无需了解线程数细节的前提下也能用好线程池,但是实际使用中似乎并不如意,还是得用回原生线程池ThreadPoolExecutor 。
关于ThreadPoolExecutor各个参数的含义,可参考我的线程池总结文章
线程池使用FutureTask的时候如果拒绝策略设置为了 DiscardPolicy和DiscardOldestPolicy并且在被拒绝的任务的Future对象上调用无参get方法那么调用线程会一直被阻塞。
35.并行和并发有什么区别?
36.线程和进程的区别?
37.守护线程是什么?