type
status
date
slug
summary
tags
category
icon
password

1 线程池

1.1 线程池作用

  1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  1. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  1. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
  1. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行

1.2 线程池5种状态

notion image
RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。
SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。
STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。TIDYING: • SHUTDOWN 状态下,任务数为0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。 • 线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。 • 线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。
TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。

1.3 七大参数

  1. int corePoolSize: 核心线程池大小,线程池中常驻线程的最大数量
  1. int maximumPoolSize: 最大核心线程池大小(包括核心线程和非核心线程)
  1. long keepAliveTime: 线程空闲后的存活时间(仅适用于非核心线程)
  1. TimeUnit unit: 超时单位
  1. BlockingQueue<Runnable> workQueue: 阻塞队列
  1. ThreadFactory threadFactory: 线程工厂:创建线程的,一般默认
  1. RejectedExecutionHandler handle: 拒绝策略

1.4 Executors

  1. newFixedThreadPool创建一个固定的长度的线程池,每当提交一个任务就创建一个线程,知道达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会创建一个新的线程继续运行队列里的任务。
  1. newSingleThreadExecutor这是一个单线程的 Executor ,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来代替它;它的特点是能确保依照任务在队列中的顺序来串行执行。
  1. newCachedThreadPool创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求正驾驶,则可以自动添加新线程,线程池的规模不存在任何限制。
  1. newScheduledThreadPool创建一个固定数量的核心线程线程池,而且以延迟或定时的方式来执行任务,类似于 Timer
  1. newSingleThreadScheduledExecutor单线程可执行周期性任务的线程池
  1. newWorkStealingPool任务窃取线程池,不保证执行顺序,适合任务耗时差异较大。线程池中有多个线程队列,有的线程队列中有大量的比较耗时的任务堆积,而有的线程队列却是空的,就存在有的线程处于饥饿状态,当一个线程处于饥饿状态时,它就会去其它的线程队列中窃取任务。解决饥饿导致的效率问题。默认创建的并行 level 是 CPU 的核数。主线程结束,即使线程池有任务也会立即停止。
为什么不推荐Executors
Executors工具类创建的线程池队列或线程默认为Integer.MAX_VALUE,容易堆积请求 阿里巴巴Java开发手册:
  • FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
  • CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
推荐使用ThreadPoolExecutor类根据实际需要自定义创建

1.5 四大策略

拒绝策略就是当队列满时,线程如何去处理新来的任务。

AbortPolicy(中止策略)-默认

  • 功能:当触发拒绝策略时,直接抛出拒绝执行的异常
  • 使用场景:ThreadPoolExecutor中默认的策略就是AbortPolicy,由于ExecutorService接口的系列ThreadPoolExecutor都没有显示的设置拒绝策略,所以默认的都是这个。

CallerRunsPolicy(调用者运行策略)

  • 功能:果线程池已经关闭或者已经达到饱和状态,新提交的任务将由提交任务的线程来执行
  • 使用场景:一般在不允许失败、对性能要求不高、并发量较小的场景下使用。

DiscardPolicy(丢弃策略)

  • 功能:直接丢弃这个任务,不触发任何动作
  • 使用场景: 提交的任务无关紧要,一般用的少。

DiscardOldestPolicy(弃老策略)

  • 功能:如果任务被拒绝,它将丢弃工作队列中最旧的任务(即最早进入队列的任务),然后重试执行任务。
  • 使用场景:发布消息、修改消息类似场景。当老消息还未执行,此时新的消息又来了,这时未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。也适合于缓存场景。

1.6 工作队列

  • ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出
  • LinkedBlockingQueue:使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为Interger.MAX_VALUE
  • PriorityBlockingQueue:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列
  • DelayQueue:延时队列,内部元素必须实现Delayed接口,只有在延迟期满时才能取出元素。这种队列适用于需要延迟执行任务的场景。
  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

1.7 运行流程

notion image
判断线程池里的核心线程是否都在执行任务?
  • 否:调用/创建一个新的核心线程来执行任务
  • 是:工作队列是否已满?
    • 否:将新提交的任务存储在工作队列里
    • 是:线程池里的线程数是否达到最大线程值?
      • 否:调用/创建一个新的非核心线程来执行任务
      • 是:执行线程池饱和策略
 
1. 一个线程池 core 7; max 20 , queue: 50, 100并发进来怎么分配的?
答:先有7个能直接得到执行, 接下来把50个进入队列排队等候, 在多开13个继续执行。 现在 70 个被安排上了。 剩下 30 个默认执行饱和策略。
2. 线程池创建之后,会立即创建核心线程吗?
不会。在刚刚创建ThreadPoolExecutor 的时候,线程并不会立即启动,而是要等到有任务提交时才会启动,除非调用了prestartCoreThread/prestartAllCoreThreads 事先启动核心线程。
线程池执行任务
execute提交没有返回值,不能判断是否执行成功。只能提交一个Runnable的对象submit会返回一个Future对象,通过Future的get()方法来获取返回值,submit提交线程可以吃掉线程中产生的异常,达到线程复用。当get()执行结果时异常才会抛出。原因是通过submit提交的线程,当发生异常时,会将异常保存,待future.get()时才会抛出。
关闭线程池
  • shutdown():不再继续接收新的任务,执行完成已有任务后关闭
  • shutdownNow():直接关闭,若果有任务尝试停止

2 多线程

2.1 线程状态

  1. 新建(New): 当我们创建了一个线程对象时,该线程就处于新建状态。它还没有开始运行,只是被分配了必要的系统资源。
  1. 可运行(Runnable): 当线程对象被启动后,该线程就处于可运行状态。可运行的线程可能正在运行也可能没有运行,这取决于操作系统给予线程的CPU时间。
  1. 阻塞(Blocked): 当一个线程试图获取一个内部的对象锁(而那个锁可能被其他线程持有),或者一个线程在等待操作系统的资源,那么该线程就处于阻塞状态。当其他线程释放锁或者该线程的请求得到满足时,该线程将返回到可运行状态。
  1. 等待(Waiting): 线程因为某些条件没有满足,比如等待其他线程完成特定任务,而处于等待状态。线程在等待时会释放所有占用的资源,比如锁。这是一个无限期的等待,需要被其他线程显式唤醒。
  1. 计时等待(Timed Waiting): 这是等待状态的一种特殊形式,它会等待特定的时间,或者等待一个特定的时间事件。例如,调用了sleep或wait指定时间的方法,或者等待某个时间的输入/输出完成。
  1. 终止(Terminated): 当线程的run方法执行完毕,或者线程被中断,那么该线程就处于终止状态。一旦线程进入终止状态,就不能再回到其他状态。

2.2 线程方法

2.2.1 Thread类常用方法

  1. start(): 启动一个新的线程并执行它的 run() 方法。
  1. run(): 这是你需要覆盖的方法,以定义线程应该执行什么操作。当线程被启动并调用其 start() 方法时,run() 方法将被调用。
  1. join(): 使当前线程等待,直到调用join的线程完成,除非线程被中断。
  1. sleep(long millis): 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统的计时器和调度器的精度和准确性。
  1. yield(): 暂停当前正在执行的线程对象,并执行其他线程。它可以使一个线程从运行状态转变为可运行状态,以允许相同优先级的其他线程获得运行的机会。
  1. isAlive(): 测试线程是否处于活动状态。活动状态是指线程已经启动但尚未终止。
  1. currentThread(): 返回对当前正在执行的线程对象的引用。
  1. interrupt(): 中断此线程。这会影响到正在等待、睡眠或其他阻塞状态的线程,使其抛出 InterruptedException
  1. isInterrupted(): 测试此线程是否已被中断。

2.2.2 取消线程的方法

  1. 中断
而 Thread.interrupt 的作用其实不是中断线程,而是通知线程应该中断了
具体到底中断还是继续运行,应该由被通知的线程自己处理。
具体来说,当对一个线程,调用 interrupt() 时,
① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
如果一个线程有中断需求,可以在线程执行的时候检查中断标识
  1. 使用Future和ExecutorService

2.2.3 wait和notify

这两个方法必须在同步块或同步方法中使用,也就是当前线程持有锁的状态。
  1. wait() 方法:这个方法使当前线程进入等待状态,并释放该线程所持有的所有同步资源。当其他线程调用同一个对象的 notify() 方法并释放同步资源时,这个等待的线程会被唤醒并重新竞争获取同步资源,如果成功则继续执行。
  1. notify() 方法:这个方法会唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待,它会选择唤醒其中一个(选择规则是不确定的),notifyAll() 是唤醒全部。注意,调用 notify() 方法后,并不立即释放对象锁,只有等到同步代码块执行完毕后才会释放。因此,即使你调用了 notify() 方法,等待状态的线程也得不到执行,直到当前线程执行完同步代码块。

2.2.4 volatile

volatile修饰的共享变量,就具有了以下两点特性:
1 . 保证了不同线程对该变量操作的内存可见性;
2 . 禁止指令重排序(有序性)
有序性实现原理:java定义了happens-before,其中volatile修饰的变量不会重新排序。
可见性实现:通过内存屏障实现
当写一个volatile变量时,JMM会把该线程对应的本地内存(工作内存)中的共享变量刷新到主内存
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。
使用场景:双重检查锁单例、状态量标记(变量的单纯读写操作可以认为是线程安全的)

2.2.5 synchronized和Lock

synchronized用法:可修饰方法、静态方法、代码块(传入变量或类对象)
synchronized原理:
对象存储在堆中,主要分为三部分内容:对象头、对象实例数据、对齐填充。
对象头中包含mark word字,下图是 Mark Word 的具体字段:
notion image
 
每一个锁都对应一个Monitor对象,每个对象都有一个与之关联的Monitor对象,不同线程获取到锁(Monitor对象)才能够执行代码
synchronized优化:synchronized一共有四种锁状态:无锁、偏向锁、轻量级锁(cas)、重量级锁
偏向锁:偏向锁假定将来只有第一个申请锁的线程会使用锁,第一次申请锁时会将mark word设置成当前的threadid,后续申请时检测这个标志位,如果相同则直接拿到锁,若不同则升级为轻量级锁。偏向锁只需要在第一次申请的时候做一次cas操作。
lock 优点
  • 提供了比synchronized更多的功能,如可以判断锁是否被持有,可以被中断的锁等。
  • 支持公平锁,即等待时间最长的线程会优先获取到锁。
  • 支持多个条件变量,即一个锁可以有多个等待队列。
lock 缺点
  • 使用相对复杂,需要在try/finally代码块中手动获取和释放锁,否则可能会导致锁未被正确释放。
  • 相比synchronizedLock的性能并不一定就会更好。

2.2.6 线程安全的集合

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
sleep和wait的区别
  1. sleep不释放锁,wait释放锁
  1. sleep必须指定时间,wait可以指定也可以不指定
  1. sleep可以在任意位置,wait必须在同步方法或代码块中

3 JVM

栈运行,
编程基础-死锁输出Git日志脚本
LuluNotion
LuluNotion
一个普通的干饭人🍚
公告
type
status
date
slug
summary
tags
category
icon
password
🎉NotionNext 4.0即将到来🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏