线程池
一、线程池的 使用以及优势
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务
如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:
线程复用,控制最大并发数,管理线程。
优点:
- 降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
二、线程池3个常用方式
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。
了解:
- Executors.newScheduledThreadPool()
- Executors.newWorkStealingPool(int) - Java8新增,使用目前机器上可用的处理器作为它的并行级别
掌握:
1.Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
主要特点如下:
- 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
- newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue。
2.Executors.newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
主要特点如下:
- 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue。
3.Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
主要特点如下:
- 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
测试:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author zhangzhixi * @date 2021-5-2 0:26 */ public class Test_09_创建线程池的3种方式 { public static void main(String[] args) { // 一池5个处理线程(用池化技术,一定要记得关闭) // ExecutorService threadPool = Executors.newFixedThreadPool(5); // 创建一个只有一个线程的线程池 // ExecutorService threadPool = Executors.newSingleThreadExecutor(); /**创建一个拥有N个线程的线程池,根据调度创建合适的线程*/ ExecutorService threadPool = Executors.newCachedThreadPool(); // 模拟10个用户来办理业务,每个用户就是一个来自外部请求线程 try { // 循环十次,模拟业务办理,让5个线程处理这10个请求 for (int i = 1; i <= 10; i++) { final int tempInt = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }
输出结果:
三、线程池7大参数介绍
/* * @param corePoolSize 线程池中的常驻核心线程数 * @param maximumPoolSize 线程池能够容纳同时执行的最大线程数,此值必须大于等于1 * @param keepAliveTime 多余的空闲线程的存活时间。 * @param unit 线程时间的单位 * @param workQueue 任务队列,被提交但尚未被执行的任务。 * @param threadFactory 表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。 * @param handler 拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数( maximumPoolSize)。 */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
举例:
public static void main(String[] args) { /*可以将创建线程池当做一个银行的业务办理窗口*/ ExecutorService threadPool = new ThreadPoolExecutor( // int corePoolSize, 核心线程池大小(候客区窗口2个) 2, // int maximumPoolSize, 最大核心线程池大小(总共5个窗口) 5, // long keepAliveTime, 超时3秒没有人调用就会释,放关闭窗口 3, // TimeUnit unit, 超时单位 秒 TimeUnit.SECONDS, // 阻塞队列(候客区最多3人) new LinkedBlockingDeque<>(3), // 默认线程工厂 Executors.defaultThreadFactory(), /*4种拒绝策略之一:队列满了,尝试去和 最早的竞争,也不会抛出异常!*/ new ThreadPoolExecutor.DiscardOldestPolicy() ); //队列满了,尝试去和最早的竞争,也不会抛出异常! try { // 最大承载:Deque + max // 超过 RejectedExecutionException for (int i = 1; i <= 9; i++) { // 使用了线程池之后,使用线程池来创建线程 threadPool.execute(() -> { System.out.println( Thread.currentThread().getName() + " ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { // 线程池用完,程序结束,关闭线程池 threadPool.shutdown(); } }
线程池底层工作原理:
线程池的四种拒绝策略:
等待队列也已经排满了,再也塞不下新任务了同时,线程池中的max线程也达到了,无法继续为新任务服务。
这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK拒绝策略:
- AbortPolicy(默认):直接抛出 RejectedExecutionException异常阻止系统正常运知。
- CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
四、死锁现象以及解决方案
什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去
如果系统资源充足,进程的资源请求都能够碍到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
产生死锁主要原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
发生死锁的四个条件:
- 互斥条件,线程使用的资源至少有一个不能共享的。
- 至少有一个线程必须持有一个资源且正在等待获取一个当前被别的线程持有的资源。
- 资源不能被抢占。
- 循环等待。
如何解决死锁问题
- 破坏发生死锁的四个条件其中之一即可。
死锁代码:
1 /** 2 * @author zhangzhixi 3 * @date 2021-5-2 20:51 4 */ 5 public class Test_11_死锁现象 { 6 public static void main(String[] args) { 7 Object o1 = "AAA"; 8 Object o2 = "BBB"; 9 10 new Thread(new DeadLock(o1, o2), o1.toString()).start(); 11 new Thread(new DeadLock(o2, o1), o2.toString()).start(); 12 } 13 } 14 15 class DeadLock implements Runnable { 16 17 Object o1; 18 Object o2; 19 20 public DeadLock(Object o1, Object o2) { 21 this.o1 = o1; 22 this.o2 = o2; 23 } 24 25 @Override 26 public void run() { 27 synchronized (o1) { 28 System.out.println(Thread.currentThread().getName() + "持有锁:" + o1 + ",\t想要获得锁:" + o2); 29 synchronized (o2) { 30 System.out.println(Thread.currentThread().getName() + "持有锁:" + o2 + ",\t想要获得锁:" + o1); 31 } 32 } 33 } 34 }
查看是否死锁工具:
-
jps命令定位进程号
-
jstack找到死锁查看