线程总结

一:进程、线程、协程的区别

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

e2c63892a81719fc02932942aac207bd

 

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

bde6ddedee2957e26dd2161954853081

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;
    }

 

 线程的6种状态1

 

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_WAITING12。

 

五:线程池的创建

 

        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();
            }
        }
    }

 

七.线程池的执行原理

aae6bc09d99e2b22dd8df0469a5825ab

 

八.线程池工作线程和普通线程有什么区别

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

 

posted @ 2025-10-09 20:03  爵士灬  阅读(1)  评论(0)    收藏  举报