多线程
线程的生命周期
状态 | 描述 |
---|---|
【NEW】 | 这个状态主要是线程未被Thread.start()调用前的状态。 |
【RUNNABLE】 | 线程正在JVM中被执行,等待来自操作系统(如处理器)的调度。 |
【BLOCKED】 | 阻塞,因为某些原因不能立即执行需要挂起等待。 |
【WAITING】 | 无限期等待,由于线程调用了Object.wait(0) ,Thread.join(0) 和LockSupport.park 其中的一个方法,线程处于等待状态,其中调用wait , join 方法时未设置超时时间。 |
【TIMED_WAITING】 | 有限期等待, 线程等待一个指定的时间,比如线程调用了Object.wait(long) , Thread.join(long) ,LockSupport.parkNanos , LockSupport.parkUntil 方法之后,线程的状态就会变成TIMED_WAITING |
【TERMINATED】 | 终止的线程状态,线程已经完成执行。 |
在操作系统层面RUNNABLE分为RUNNING和READY状态,但是操作系统的调度方式是 时间分片方式进行抢占式轮转调度 然而这个时间片是非常小的运行大概只有0.01s这个量级,运行之后又放在队列末尾进行等待调度,因此JVM就没有区分这两种状态,都归纳为RUNNABLE状态
线程池
线程池可以通过 java.util.concurrent
包中的 ExecutorService
和 ThreadPoolExecutor
类来实现
1. FixedThreadPool(固定大小线程池)
-
特点:该线程池包含固定数量的线程,如果所有线程都在工作,新的任务会被放入任务队列中等待。
-
适用场景:适用于负载稳定、长期运行的应用场景,适合控制线程并发数避免资源占用过多。
-
创建方法
:
1 2 3 4 5
java 复制代码 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int nThreads);
2. CachedThreadPool(缓存线程池)
-
特点:线程池会根据需要创建新线程,空闲线程会被回收。当线程池中的线程空闲时间超过 60 秒,线程将被终止并移出池。线程池会动态调整大小以应对较高的并发需求。
-
适用场景:适用于执行大量短期异步任务,或负载波动较大的任务。由于线程数量没有上限,可能导致系统资源耗尽。
-
创建方法
:
1 2 3 4 5
java 复制代码 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
3. SingleThreadExecutor(单线程线程池)
-
特点:该线程池只有一个工作线程,用于确保任务按顺序执行。如果这个线程异常终止,会有一个新的线程代替它继续执行后续任务。
-
适用场景:适用于需要确保顺序执行任务的场景,比如顺序写入日志文件的操作。
-
创建方法
:
1 2 3 4 5
java 复制代码 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
4. ScheduledThreadPool(定时任务线程池)
-
特点:用于定时或周期性执行任务,类似于
Timer
类的功能,但功能更加强大。 -
适用场景:适用于需要定时任务调度的场景,比如定时执行某个任务或周期性执行。
-
创建方法
:
1 2 3 4 5
java 复制代码 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
5. WorkStealingPool(工作窃取线程池)
-
特点:基于工作窃取算法的线程池,充分利用多核 CPU,减少线程间的竞争,提升并行任务的执行效率。
-
适用场景:适用于大量并行的小任务,例如
ForkJoinPool
的应用场景。 -
创建方法
(Java 8 引入):
1 2 3 4 5
java 复制代码 ExecutorService workStealingPool = Executors.newWorkStealingPool();
哪个线程池最好?
没有绝对“最好的”线程池,选择合适的线程池取决于具体的应用场景和需求。以下是一些选择建议:
- FixedThreadPool:适合任务量固定、长期运行的场景,可以通过固定线程数量控制系统资源使用。
- CachedThreadPool:适合任务量波动较大、执行时间短的任务,但要注意线程数量无限制,可能耗尽资源。
- SingleThreadExecutor:适合需要确保顺序执行任务的场景,如写日志、串行操作。
- ScheduledThreadPool:适合定时任务或周期性任务。
- WorkStealingPool:适合并行计算任务多的场景,尤其在多核 CPU 环境下表现良好。
因此,线程池的选择需要根据实际任务的并发特性、执行时长、调度要求来决定。
自定义线程池参数
1. corePoolSize(核心线程数)
- 定义:线程池中始终保持的线程数量,即使它们处于空闲状态。核心线程会处理任务,当核心线程数不足时,新的任务会放入任务队列中。
- 作用:控制线程池在没有任务时的最小线程数。
- 建议:适当设置为机器可用的 CPU 核心数量或稍微多一些,具体依据任务的计算密集型或 I/O 密集型而定。
2. maximumPoolSize(最大线程数)
- 定义:线程池允许创建的最大线程数。当任务队列已满,且当前线程数小于
maximumPoolSize
时,线程池会创建新的线程执行任务。 - 作用:限制线程池的最大并发线程数,防止线程数量过多导致系统资源耗尽。
- 建议:
maximumPoolSize
应大于或等于corePoolSize
,并结合任务负载以及系统资源来合理设定。
3. keepAliveTime(空闲线程存活时间)
- 定义:当线程数超过
corePoolSize
时,多余的空闲线程最多可以存活的时间。超过这个时间,空闲线程将会被销毁。如果线程数等于corePoolSize
,空闲线程不会被销毁。 - 作用:用于控制线程池中非核心线程的回收时机,从而节省系统资源。
- 建议:适当设置可提高线程池的灵活性,避免频繁创建和销毁线程。
4. unit(时间单位)
- 定义:
keepAliveTime
参数的时间单位,可以是TimeUnit
枚举中的任意一个值,例如TimeUnit.SECONDS
、TimeUnit.MILLISECONDS
等。
5. workQueue(任务队列)
-
定义
:用于保存等待执行任务的阻塞队列,当所有核心线程都在工作时,新的任务会进入此队列等待。常用的任务队列类型有:
- SynchronousQueue:不保存任务,直接将任务交给线程执行。如果没有线程可用,会创建新线程。
- LinkedBlockingQueue:基于链表的有界或无界阻塞队列,默认无界,会导致线程数永远不会超过
corePoolSize
。 - ArrayBlockingQueue:基于数组的有界阻塞队列,需指定队列大小。
- PriorityBlockingQueue:带优先级的无界阻塞队列,任务会根据优先级顺序执行。
-
作用:决定任务在没有空闲线程时的排队方式,直接影响线程池的行为。
6. threadFactory(线程工厂)
- 定义:用于创建新线程的工厂,默认是
Executors.defaultThreadFactory()
,该工厂创建的线程都是非守护线程,且具有默认优先级。 - 作用:允许自定义线程的创建,比如给线程命名、设置优先级、是否为守护线程等。
- 建议:可以通过自定义
ThreadFactory
为线程命名,便于在排查问题时区分不同的线程池。
7. handler(拒绝策略)
-
定义
:当线程池无法处理新任务时的拒绝策略。通常在任务队列已满且线程数达到了
1
maximumPoolSize
时会触发。内置的拒绝策略有:
- AbortPolicy:默认策略,抛出
RejectedExecutionException
异常。 - CallerRunsPolicy:由调用线程执行任务,即提交任务的线程自己执行任务,避免任务丢失。
- DiscardPolicy:直接丢弃任务,不抛出异常。
- DiscardOldestPolicy:丢弃队列中最老的任务,并尝试重新执行当前任务。
- AbortPolicy:默认策略,抛出
-
作用:控制线程池任务满载时的处理方式,防止系统资源耗尽或任务丢失。
-
建议:根据业务场景选择合适的拒绝策略,确保任务不会丢失或系统不会崩溃。
JUC
synchronize关键字
java内置关键字 无法判断锁的状态 会自动释放锁
可重入锁 不可以中断 非公平锁
适合锁少量的代码同步问题
lock类 (重点)
Lock是JUC中的类 可以获取锁的状态 必须手动释放锁,要不然会发生死锁
可重入锁 公平锁
适量锁大量的代码同步问题
synchronize wait notify
lock condition.await condition.singnalAll 精准通知和唤醒
Collections.syc
ConcurrentArrayList
ConcurrentHashSet
ConcurrentHashMap
CopyOnWriteHashMap