多线程的使用
继承Thread ,实现Runable,实现Callable 传入FutureTask中
package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; class MyThread extends Thread{ @Override public void run() { System.out.println("继承Thread实现多线程"+Thread.currentThread().getName()); } } class MyRunable implements Runnable{ @Override public void run() { System.out.println("实现Runnable实现多线程"+Thread.currentThread().getName()); } } class MyCallable implements Callable<String> { @Override public String call() throws Exception { TimeUnit.SECONDS.sleep(3); System.out.println("实现MyCallable实现多线程"+Thread.currentThread().getName()); return "callAble"; } } /** * @author liuxu * @date 2021/11/14 19:53 */ public class ThreadDemo { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("AA"); myThread.start(); Thread thread = new Thread(new MyRunable()); thread.setName("BB"); thread.start(); //FutureTask实现了Runable 所以和Runable一样使用只不过可以拿到返回值,阻塞返回 FutureTask<String> futureTask = new FutureTask(new MyCallable()); FutureTask<String> futureTask1 = new FutureTask(new MyCallable()); Thread thread1 = new Thread(futureTask); Thread thread2 = new Thread(futureTask1); thread1.start(); thread2.start(); try { String s = futureTask.get(); String s1 = futureTask1.get(); System.out.println(s); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
线程池的工作主要是控制线程的数量,处理过程中将任务放入队列,然后在线程池创建后启动这些任务,如果线程池数量超过了最大 数量,超出数量的线程排队等候,等待其他线程执行完毕,再从队列中取出任务来执行。
主要特点是: 线程复用,控制最大并发数,管理线程
优点:
降低资源消耗 通过复用降低了线程创建和销毁造成的消耗
提高响应速度 任务到达时,如果有空闲线程不需要创建线程就可以立即执行
提高线程的可管理性 可以对线程资源统一分配,优化和监控。
线程池的相关接口
常用线程池的使用方法
一共五个
- //创建单核心的线程池
- ExecutorService executorService = Executors.newSingleThreadExecutor();
- //创建固定核心数的线程池,这里核心数 = 2
- ExecutorService executorService = Executors.newFixedThreadPool(2);
- //创建一个按照计划规定执行的线程池,这里核心数 = 2
- ExecutorService executorService = Executors.newScheduledThreadPool(2);
- //创建一个自动增长的线程池
- ExecutorService executorService = Executors.newCachedThreadPool();
- //创建一个具有抢占式操作的线程池
- ExecutorService executorService = Executors.newWorkStealingPool();
下面是常用案例
package com.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author liuxu * @date 2021/11/14 21:29 */ public class ExcutorDemo { public static void main(String[] args) { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//固定大小线程池 适用长期任务 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//单个线程 适用一个一个任务执行的场景 ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //大小自适应 适用多短期异步小程序或者负载比较轻的服务器 try{ for (int i = 0; i < 10; i++) { /*fixedThreadPool.execute(()->{ //实现Ranable的内部类 System.out.println(Thread.currentThread().getName()+"\t 固定线程数线程办理业务"); });*/ /*singleThreadExecutor.execute(()->{ System.out.println(Thread.currentThread().getName()+"\t 单线程办理业务"); });*/ cachedThreadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"\t 自适应大小单线程办理业务"); }); } }catch (Exception e){ e.printStackTrace(); }finally { //fixedThreadPool.shutdown(); // singleThreadExecutor.shutdown(); cachedThreadPool.shutdown(); } } }
底层原理
ExecutorService构造 可以看出5大参数
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } //点进去可以看到7个参数 public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数 int maximumPoolSize,//能够容纳同时执行的线程数,此值必须大于1 long keepAliveTime,//多余的空闲线程的存活时间 TimeUnit unit,// 存活时间的时间单位 //当线程数超过corePoolSize 有空闲线程且空闲线程存活时间大于keepAliveTime, //空闲线程就会销毁 直到线程数等于 corePoolSize BlockingQueue<Runnable> workQueue, //任务队列,存放已提交但是未被执行的任务 ThreadFactory threadFactory,//创建线程的工厂,用默认的的就行 RejectedExecutionHandler handler) //拒绝策略, 队列满切工作线程大于maximumPoolSize时提交的任务的处理方式
调整线程池的7个参数可以设置不同的线程工作状态
状态1
添加的任务小于核心线程数 线程池中线程功能数
状态 | 添加任务个数 | 线程池中运行的线程个数 | 阻塞队列中任务 | 说明 |
状态1 | <corePoolSize | corePoolSize | 无 | 此时来任务会马上调用创建好的核心线程执行任务 |
状态2 | >=corePoolSize | >corePoolSize | 有 | 此时新加任务会被放入队列,如果有线程执行任务完毕,将从队列中拉取任务执行,此时无空闲线程, |
状态3 | 队列已满且运行线程数量<maximumPoolSize | corePoolSize + | 有且已经最大 | 线程池会扩容但是小于maximumPoolSize,立即执行新添加的任务 |
状态4 | 队列已满且运行线程数量=maximumPoolSize | maximumPoolSize | 有且已经最大 | 拒绝策略 |
当线程完成任务时,他会从队列中获取一个任务 |
package com.thread; import java.util.concurrent.*; /** * @author liuxu * @date 2021/11/15 21:22 */ public class ThreadPoolDemo { public static void main(String[] args) { /** *如输出展示 * 8个任务进来 * 1 2被核心线程接受处理 * 3 4 5 等待区等待 * 6 7 8 任务进来时队列满座 * 新开3个线程达到最大线程 处理 6 7 8 * 最后 先完成任务的线程 再从队列中拉取 3 4 5执行 */ ExecutorService service = new ThreadPoolExecutor(2, 5, 100L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); try { for (int i = 1; i <= 8; i++) { int tem = i; service.execute(() -> { System.out.println(Thread.currentThread().getName() + "线程处理任务" + tem); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }); } } finally { service.shutdown(); } } }
线程池工作原理总结
1.创建线程池,大小为核心线程池大小,等待任务 Runable接口来临
2.任务来临,调用execute方法,做如下判断
2.1 如果当前运行的线程数小于核心线程数,立即使用创建好的核心线程执行任务
2.2 如果任务大于等于核心线程数,新来的任务放入队列
2.3 如果此时队列已满,且线程数小于最大线程数,立即创建线程执行任务
2.4 如果队列已满且线程数小于等于最大线程数,执行拒绝策略
3.当一个线程空闲达到一定时间,会做出判断
如果线程大于核心线程数,这个线程会被停止
所有任务完成后,线程池会缩减为核心线程池大小
线程池的拒绝策略
JDK内置的拒绝策略有四种 都继承了RejectedExecutionHandler
new ThreadPoolExecutor.AbortPolicy() //直接 抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()//一种调节机制 ,建将任务返回给调用线程
new ThreadPoolExecutor.DiscardOldestPolicy());// 丢弃队列中等待最久的任务
new ThreadPoolExecutor.DiscardPolicy());//新来任务直接丢弃
可以在上面的代码中将最后一个参数替换后尝试效果
工作中实际上使用哪一个线程池
new ThreadPoolExecutor()明确的指定参数
如何合理配置线程池大小
CPU密集型 :CPU核数+1 while (true)
IO秘籍型 读取数据频繁 ,读取文件频繁: CPU核数*2 或者 CPU核数/(1-阻塞系数)
阻塞系数在0.8-0.9之间 即 10-12.5倍的 CPU核数
死锁
现象:线程A 持有锁A实体获取锁B 线程B持有锁B尝试获取锁A
原因:系统资源不足,进程推进顺序 不合适,资源分配不当
死锁代码展示
package com.thread; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class HoldThread implements Runnable{ private String lockA; private String lockB; public HoldThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+"持有"+lockA+"尝试获取"+lockB); try { synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "持有" + lockB + "尝试获取" + lockA); } }catch (Exception e){ e.printStackTrace(); } } } } /** * @author liuxu * @date 2021/11/16 20:42 */ public class deadLockDemo { public static void main(String[] args) { new Thread(new HoldThread("A","B"),"A").start(); Thread thread = new Thread(new HoldThread("B", "A") , "B"); thread.start(); //死锁 /*try { //非死锁 TimeUnit.SECONDS.sleep(3); thread.start(); } catch (InterruptedException e) { e.printStackTrace(); }*/ } }
查找死锁位置
jps -l 找到死锁线程
jstack 22312(死锁线程号)找到死锁代码