作用:Callable接口详解
优势:
Runnable与Callable的区别
2种执行方式:
概念:控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,那么将进入阻塞队列中等候。
特点:线程复用,控制最大并发数,管理线程;
优势:
1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁中造成的消耗; 2. 提高响应速度。当任务到达时,任务不需要等待线程创建就能立即执行; 3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,会消耗系统资源,降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
底层实现:ThreadPoolExecutor
使用int的高3位表示线程池状态,低29位表示线程数量。
状态名 | 高三位 | 接收新任务 | 处理阻塞队列任务 | 备注 |
---|---|---|---|---|
RUNNING | 111 | Y | Y | |
SHUTDOWN | 000 | N | Y | 不会接收新任务,但会处理阻塞队列剩余任务 |
STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
TIDYING | 010 | - | - | 所有任务执行完毕,活动线程为0即将进入终结 |
TERMINATED | 011 | - | - | 终结状态 |
newFixedThreadPool
: 核心线程数=最大线程数,无救急线程。 暴露threadFactory
,可以对线程进行创建与修改;
newCacheThreadPool
: 核心线程数是0,最大线程数是int最大,意味着全部都是救急线程。救急线程的生命周期是60s。救急线程使用完便会销毁;
newSingleThreadExecutor
: 仅有一个核心线程,返回FinalizableDelegatedExecutorService
暴露ThreadPoolExecutor
;
注意:阿里巴巴的开发手册上写到,不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险;
Executors返回的线程池对象弊端如下:
FixedThreadPool和SingleThreadPool:
允许的请求队列长度为整形最大值,可能会堆积大量的请求导致OOM;
CachedThreadPool和ScheuledThreadPool:
允许创建线程数量为整形最大值,可能会创大量的线程,导致OOM;
使用接口:
/** * @param corePoolSize 核心线程数量 * @param maximumPoolSize 线程池允许的最大线程数量 * @param keepAliveTime 救急线程的空闲时间,救急线程一直空闲则回收,当阻塞队列满了,corePoolSize<maximumPoolSize时启用救急线程。 * @param unit 针对救急线程的时间单位 * @param workQueue 阻塞队列 * @param threadFactory 创建线程的工厂 * @param handler 拒绝策略 默认:new AbortPolicy() */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
底层实现:
newFixedThreadPool
// nThreads核心线程数 threadFactory 线程工厂 public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); } // 业务实现 ExecutorService threadPool = Executors.newFixedThreadPool(5, new ThreadFactory(){ AtomicInteger i = new AtomicInteger(0); @Override public Thread newThread(Runnable r) { Thread t = new Thread(() -> {r.run();}); t.setName("Hello"+i.incrementAndGet()); return t; } });
核心线程数=最大线程数,无救急线程。 暴露
threadFactory
,可以对线程进行创建与修改
newCacheThreadPool
//threadFactory 线程工厂 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
核心线程数是0,最大线程数是int最大,意味着全部都是救急线程。救急线程的生命周期是60s。救急线程使用完便会销毁,使用的是SynchronousQueue
newSingleThreadExecutor
//threadFactory 线程工厂 public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
仅有一个核心线程,返回
FinalizableDelegatedExecutorService
暴露ThreadPoolExecutor
线程池的工作原理:
AbortPolicy
: 让调用者抛出RejectedExecutionException
异常<默认>
CallerRunsPolicy
:让调用者运行任务
DiscardPolicy
:放弃本次任务
DiscardOldestPolicy
: 放弃队列中最早的任务,本任务取而代之
分两种情况,CPU密集型和IO密集型
CPU密集型:该任务需要大量的运算,没有阻塞,CPU一直全速运行,CPU高。
CPU密集任务只有在真正的多核CPU上才可能得到加速(多线程),而在单核CPU上,无论设置几个模拟的多线程任务都无法得到加速,因为CPU总的运算能力就单核的能力。
CPU密集型任务配置尽可能少的线程数量:CPU核数+1个线程的线程池;
IO密集型1:任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2;
IO密集型2:任务需要大量的IO,即大量的阻塞;在单线程上运行IO密集型的任务会导致大量的CPU运算浪费在等待。所以在IO密集型任务中使用多线程可以大大加速程序运行,即使在单核CPU上,这种加速主要是利用了被浪费掉的阻塞时间。
IO密集型时,大量线程被阻塞,配置线程数公式:
CPU核数/(1-阻塞系数) 其中阻塞系数在0.8~0.9之间
比如8核CPU: 8/(1-0.9)=80个线程数
概念:死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那将一直阻塞,如果系统资源充足,进程的资源请求都能得到满足,死锁出现的可能性会很低,否则就会因争夺有限的资源而陷入死锁.
**死锁产生的原因:**1.系统资源不足;2.进程运行的顺序异常;3.资源分配不当;
必要条件:
解决死锁的方法
预防死锁:
避免死锁:
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
检测死锁:
解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
查找进程的pid
ps aux | grep 参数 或者 pgrep -lf 参数 或者 jps 参数
jstack pid
或者用JProfiler(升级版的 JConsole )