线程总结
一:进程、线程、协程的区别
1.进程是操作系统分配和调度的最小单元,进程有独立的资源空间。
2.线程是CPU调度的最小单元,一个进程可以有多个线程
3.协程是轻量级线程,JDK21之后可以直接创建协程进而减少用户态和内存态的切换。
二:java中创建线程的方式有几种
1.创建Runnable对象使用Thread.start运行线程。
2.创建Callable对象使用Exc.submit运行线程。
3.使用线程池创建对象
4.继承Thread
三:Future和FutureTask和CompletableFuture的区别
1.Future
1.Future表示一个异步任务的结果,它提供了检查任务是否已完成、以及等待任务完成并获取结果等方法
2.get()方法阻塞获取
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(100), new ThreadPoolExecutor.CallerRunsPolicy());
Future<?> future = threadPoolExecutor.submit(() -> {
//代码逻辑执行
});
future.get();
future.cancel();
future.isCancelled();
future.isDone()
2.FutureTask
1.FutureTask实现了RunnableFuture,而RunnableFuture继承了Runnable和Future,所以FutureTask即可获取结果又可被线程执行。
2.创建FutureTask需要实现Runnable或Callable,使用Executor的submit执行Callable的call方法,使用excute执行执行Runnable的run方法,使用Thread.start启动线程。
3.get()方法阻塞获取。
//FutureTask执行Callable FutureTask callableTask = new FutureTask(new Callable() { @Override public Object call() throws Exception { return "1234"; } }); //线程执行 new Thread(callableTask).start(); try { Object o = callableTask.get(); System.out.println(o); } catch (Exception e) { e.printStackTrace(); }
//FutureTask执行Runnable FutureTask runnableTask = new FutureTask(new Runnable() { @Override public void run() { System.out.println(123); } }, false); //线程执行 new Thread(runnableTask).start(); }
3.CompletableFuture
1.CompletableFuture是Java 8引入的Future扩展,它提供了更强大的异步编程能力,支持链式调用、组合任务和回调处理。
2.可以通过thenRun() 、thenAccept()、thenApply() 方法将前后任务连接起来,形成前后有依赖的任务链。其中:
- thenRun(Runnable runnable): 对异步任务的结果进行操作,不能传入参,也没有返回值。
- thenAccept(Consumer consumer): 对异步任务的结果进行消费,可以传入参,但没有返回值。
- thenApply(Function function): 对异步任务的结果进行转换,可传入参,返回一个新的CompletableFuture。
3.并行聚合,同一超时控制
//带响应结果 CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> { //执行逻辑 return "返回响应结果"; }, threadPoolExecutor); //不带响应结果 CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> { //执行逻辑无需响应 }, threadPoolExecutor); try { //共同设置超时时间,一个失败全部失败 CompletableFuture.allOf(supplyAsync,runAsync).get(500,TimeUnit.MILLISECONDS); //带响应结果 String result = supplyAsync.get(); //不带响应结果 runAsync.get(); } catch (Exception e) { e.printStackTrace(); }
四:线程有几种状态
public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }
NEW:
1.新建状态,当线程被创建出来就是NEW
2.当线程被thread.start时就是就绪状态RUNNABLE
3.当线程分配到CPU片段时就是运行状态RUNNABLE
RUNNABLE
1.RUNNABLE分为就绪状态和运行状态,当获取到CPU执行片段就会从就绪状态转为运行状态。
2.当sleep(),wait(),LockSuppert.park()状态会变为等待状态
3.当调用sleep(xxx),wait(xxx),LockSuppert.parkNanos(xxx)状态会变为超时等待状态
WAITING
1.线程挂起等待状态,需要等线程其余线程唤起,此时不消化CUP资源
2.调用notify(),notifyAll(),unpark()唤起
TIMED_WAITING
1.线程计时等待,需要等待超时时间结束才会字段唤醒,此时不消化CUP资源
BLOCKED
1.线程阻塞状态,当锁资源被其他线程占用,本线程等待锁资源时的状态。
2.当本线程竞争到锁资源的时候,线程状态变为RUNNABLE
TERMINATED
1.线程终止状态,线程执行完毕或被异常终止的状态
static Object OBJ = new Object(); public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { String runnableStatus = Thread.currentThread().getState().name(); System.out.println("运行状态" + runnableStatus); //将线程状态变为等待状态 LockSupport.park(); //将线程状态变为超时等待状态,parkNanos()会释放锁 LockSupport.parkNanos(200000000); //进行锁竞争,此时由于thread2已获取锁所以是阻塞状态 synchronized (OBJ){ } }); Thread thread2 = new Thread(() -> { //锁竞争 synchronized (OBJ){ try { //模拟业务处理,让thread2阻塞等待 Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread2.start(); String newStatus = thread1.getState().name(); System.out.println("新建状态" + newStatus); thread1.start(); Thread.sleep(50); String waitingStatus = thread1.getState().name(); System.out.println("挂起等待状态" + waitingStatus); LockSupport.unpark(thread1); Thread.sleep(50); String timedWaitingStatus = thread1.getState().name(); System.out.println("超时等待状态" + timedWaitingStatus); Thread.sleep(150); String blockedStatus = thread1.getState().name(); System.out.println("阻塞状态" + blockedStatus); Thread.sleep(100); String terminated = thread1.getState().name(); System.out.println("终止状态" + terminated); }
---- 运行结果
新建状态NEW
运行状态RUNNABLE
挂起等待状态WAITING
超时等待状态TIMED_WAITING
阻塞状态BLOCKED
终止状态TERMINATED
五:sleep、wait、park的区别和是否释放锁
特性 | sleep() | wait() | park() |
---|---|---|---|
锁释放 | 不释放 | 释放 | 不释放 |
唤醒方式 | 超时自动唤醒 | notify() /notifyAll() |
unpark() 或中断 |
同步要求 | 无需同步块 | 需 synchronized |
无需同步块 |
精确唤醒 | 不支持 | 随机唤醒 | 支持指定线程 |
中断响应 | 抛出异常 | 抛出异常 | 直接返回(无异常) |
状态 | TIMED_WAITING |
WAITING /TIMED_WAITING |
WAITING /TIMED_WAITING 12。 |
五:线程池的创建
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), new ThreadPoolExecutor.CallerRunsPolicy());
六:到底如何设置线程池配置
1.核心线程的设置
1.确认机器配置2C4G、4C8G
2.确认业务类型是:CPU密集型、IO密集型,CPU密集型是基于内存计算较多,比如数据计算、逻辑判断等等,IO密集型是磁盘IO/网络IO,比如调用数据库、Redis、第三方接口等等,这些都有IO操作和等等时间。
CPU密集型核心线程设置一般都是机器核数±1,因为机器型号、性能存在差异具体多少核心线程性能最好需要压测判断。
IO密集型核心线程理论上有公式,但是由于业务的操作不一样,如访问MYSQL、第三方接口耗时不一样,所以无法具体评估,需要根据压测结果进行判断。线程池的配置可以自定义方法或使用第三方插件控制
2.最大线程数的设置
1.核心线程已经可以做到最大利用CPU的性能了,最大线程数一般都是等于核心线程数,如果核心线程已经找到最合理的值,最大线程在追加几个线程可能会影响接口性能。
3.最大存活时间
1.一般都是给60S,没有太多要求
4.队列长度
1.判断业务能接受接口延迟时间是多久
2.判断接口的平均耗时是多少
3.延迟时间除于接口耗时乘核心线程数
有界队列:
ArrayBlockingQueue:基于数组实现需要配合固定容量,当队列满时会触发拒绝策略,适合需要控制任务数量的场景
无界队列:
LinkedBlockingQueue:基于链表实现,最大容量是Integer.MAX_VALUE,但有可能造成OOM,适用处理速度慢但是不能拒绝的任务
LinkedBlockingQueue:也可以指定容量
无缓冲队列:
SynchronousQueue:不存储任何元素,每个插入操作必须等待对应的删除操作,生产者和消费者线程必须严格同步,适用于高吞吐常见。
优先级队列:
PriorityBlockingQueue:通过二叉树实现,确保每次操作返回队列最高/最低的元素,适用于有优先级和资源控制的常见
5.拒绝策略
1.根据业务重要程度来评估,如果是打印日志的丢就丢了,如果是核心业务一般都是交给调用线程执行
CallerRunsPolicy:将任务交给调用者
AbortPolicy:拒绝任务并排除异常
DiscardPolicy:丢弃任务
DiscardOldestPolicy:丢弃排队时间最长的任务,尝试把自己加进去
2.如果这4种拒绝策略不能满足还可以自己实现RejectedExecutionHandler
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
七.线程池的执行原理
八.线程池工作线程和普通线程有什么区别
1.线程池工作线程和普通线程本质是没有区别的,如果想用线程只能thread.start()。
2.但是线程池的工作线程封装了一个worker对象,worker对象适配了线程池的逻辑,他实现了Runnable和继承了AQS,为了满足线程池的shutdown和shutdownNow。
//线程执行方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
//worker对象
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
九.能说一下线程池有几种状态吗?
private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS;
线程状态切换
Running
1.线程池有存活线程,可接受任务
2.调用shutdown(),线程池状态变成Shutdown()状态。
3.调用shutdownNew(),线程池状态会变成stop状态。shutdownNew会中断线程并返回线队列中的任务List<Runnable>
Shutdown
1.线程池不在接受新的任务,正在执行的任务会执行完毕,队列中的任务也会执行完毕。
2.队列中的任务执行完毕变成tidying状态。
Stop
1.线程池不在接受新任务,正在执行的任务会中断,队列中的任务也不在执行。
2.线程池中执行的任务为空会变成tidying状态。
Tidying(收拾)
1.该状态表明所有的任务已终止,队列中的任务数为0。
2.terminated()执行完毕,任务状态变为terminated状态。
Terminated(终止)
1.该状态表示线程池彻底结束
十:线程池异常怎么处理知道吗?
在使用线程池处理任务的时候,任务代码可能抛出RuntimeException,抛出异常后,线程池可能捕获它,也可能创建一个新的线程来代替异常的线程,我们可能无法感知任务出现了异常,因此我们需要考虑线程池异常情况。
常见的异常处理方式:
十一:如何在线程池执行任务前后追加逻辑
1.自定义类xx继承ThreadPoolExecutor
2.实现ThreadPoolExecutor中的方法
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
历史文章:https://www.cnblogs.com/sunnycc/p/14495693.html