本文为个人总结,如有错误请评论区指出

并发和并行

并发:在同一时刻,有多个指令在单个cpu上交替执行

并行:在同一时刻,有多个指令在多个cpu上同时执行

多线程的实现方式

继承Thread类的方式实现

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建子类的对象,并启动线程

如:
继承Thread的类

public class MyThread extends Thread{

	@Override
	public void run(){
		//要执行的代码
	}
}

测试类

public class Test{
	public static void main(String[] args){
		//创建子类对象
		MyThread t1 = new MyThread();
		
		//启动线程
		t1.start();
	}
}
Thread常用成员方法
方法名称说明
String getName()返回此线程的名称
void setName(String name)设置线程的名字(构造方法也可以设置名字)
如果没有给线程设置名字,默认是:Thread-n(n是序号,从0开始)
static Thread currentThread()获取当前线程的对象
(当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程,作用是去调用main方法,并执行里面的代码,我们写的所有的代码,都是运行在main线程当中)
static void sleep(long time)让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)设置线程的优先级(1最小,10最大,默认是5,优先级越大抢占到cpu概率越大)
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()礼让线程
public static void join()插队线程

守护线程:当其他的非守护线程执行完毕后,守护线程会陆续结束
礼让线程:让当前正在执行的线程,主动让出 CPU 执行权,回到就绪状态,与其他处于就绪状态的线程重新竞争CPU执行权。
插队线程:让当前线程(调用方) 等待” 目标线程执行完毕后,再继续执行,相当于让目标线程 “插队” 到当前线程的前面执行。

实现Runnable接口的方式进行实现

  1. 定义一个类实现Runnable接口
  2. 重写里面的run方法
  3. 创建自己的类的对象
  4. 创建一个Thread类的对象,并开启线程

如:
实现Runnable的类

public class MyRun implements Runnable{

	@Override
	public void run(){
		//要执行的代码
	}
}

测试类

public class Test{
	public static void main(String[] args){
		//创建子类对象,表示多线程要执行的任务
		MyRun run1 = new MyRun();
		
		//创建线程对象,并传入自己定义的实现Runnable的子类的对象
		MyThread t1 = new Thread(run1);
		
		//启动线程
		t1.start();
	}
}

可以用Thread.currentThread()获取当前线程的对象

利用Callable接口和Future接口方式实现

  1. 创建一个类实现Callable接口
  2. 重写call(有返回值,表示多线程运行的结果)
  3. 创建实现Callable接口的类的对象(表示多线程要执行的任务)
  4. 创建FutureTask的对象(管理多线程运行的结果)
  5. 创建Thread类的对象并启动

如:
实现Callable的类

public class MyCallable implements Callable<返回值类型>{

	@Override
	public 返回值类型 call(){
		//要执行的代码
	}
}

测试类

public class Test{
	public static void main(String[] args){
		//创建子类对象,表示多线程要执行的任务
		MyCallable mc = new MyCallable();
		
		//创建FutureTask对象,并传入自己定义的实现MyCallable的子类的对象
		FutureTask<返回值类型> ft = new FutureTask<>(mc);
		
		//创建Thread对象,并传入FutureTask对象
		Thread t = new Thread(ft);
		
		//启动线程
		t.start();

		//获取多线程运行的结果
		返回值类型 result = ft.get();
	}
}

线程的6种状态

JDK 官方Thread.State枚举的6 个标准状态:

  1. NEW(新建) :创建线程对象,调用start()前,线程未启动、未执行任何代码;
  2. RUNNABLE(就绪 / 运行) :调用start()后,线程就绪等 CPU 执行权 或 正在执行run()方法,就绪和运行合并为该状态;
  3. BLOCKED(阻塞) :争抢synchronized锁失败,被动等待获取锁,抢到锁即可恢复运行;
  4. WAITING(无限等待) :调用wait()/ 无参join()等,主动释放锁永久等待,必须被其他线程唤醒才能恢复;
  5. TIMED_WAITING(计时等待) :调用sleep()/ 带参wait()/ 带参join()等,主动休眠 / 等待,超时自动恢复 或 被提前唤醒;
  6. TERMINATED(终止) :线程run()执行完毕 或 抛未捕获异常,生命周期永久结束,不可重启。

等待唤醒机制

等待唤醒机制是 Java 中基于 Object 类的 wait()、notify()、notifyAll() 三个方法实现的线程间通信机制,必须配合 synchronized 同步代码块、同步方法使用。

特性Object.wait()Thread.sleep(long)
所属类Object 类(所有对象可调用)Thread 类(静态方法)
锁资源调用后立即释放锁调用后不释放锁
线程状态RUNNABLE→ WAITINGRUNNABLE→ TIMED_WAITING
使用前提必须在 synchronized 中使用无任何前提,任意位置调用

线程池

线程池(ThreadPoolExecutor)是java juc并发包 (java.util.concurrent) 中提供的一种线程复用机制。
本质:一个管理一组预先创建好的线程的容器。线程池会在初始化时创建一批核心线程,线程执行完任务后不会立即销毁,而是回到线程池中成空闲状态,等待下一个任务到来时直接复用。

线程池的优势

  1. 降低资源消耗
    创建线程、销毁线程的开销非常大,线程池通过线程复用,避免了频繁创建、销毁线程的开销,极大降低系统资源消耗。
  2. 提高响应速度
    任务到达时,无需等待线程创建,直接复用线程池中的空闲线程执行任务
  3. 统一管理线程
    可以精准控制并发线程的数量,避免无限制创建线程(比如循环 new 1000 个线程)导致的 CPU、内存耗尽、
  4. 支持任务扩展
    线程池提供了丰富的 API,支持任务排队、任务拒绝、线程超时回收、线程活跃数监控等功能,原生线程做不到。

线程池的工作原理

  1. 判断 核心 线程池是否已满:若线程池中正在运行的线程数 < 核心线程数,则直接新建核心线程执行任务;
  2. 核心线程池已满,判断任务队列是否已满:如果核心线程都在工作,则把任务加入阻塞队列排队,核心线程执行完当前任务后,会从队列中取任务继续执行;
  3. 任务队列已满,判断线程池是否已满:如果队列排满了,新建非核心线程执行任务;
  4. 线程池已满,触发拒绝策略:如果核心线程 + 非核心线程已达到最大线程数,队列也满了,那么线程池无法处理新任务,执行任务拒绝策略。

线程池的7个核心参数

public ThreadPoolExecutor(
    int corePoolSize,        // 1.核心线程数
    int maximumPoolSize,     // 2.最大线程数
    long keepAliveTime,      // 3.空闲存活时间
    TimeUnit unit,           // 4.时间单位
    BlockingQueue<Runnable> workQueue, // 5.任务阻塞队列
    ThreadFactory threadFactory,       // 6.线程工厂
    RejectedExecutionHandler handler    // 7.拒绝策略
)

1. corePoolSize 核心线程数
线程池的常驻线程数量,线程池初始化后默认是空的,当任务来临时才会创建核心线程,核心线程的特点:一旦创建,即使空闲也不会销毁

2. maximumPoolSize 最大线程数
线程池允许创建的最大线程总数 = 核心线程数 + 非核心线程数。

3. keepAliveTime 空闲存活时间 + 4. TimeUnit 时间单位
给非核心线程设置的超时回收规则:当非核心线程空闲的时间超过 keepAliveTime,就会被线程池销毁,释放资源。

  • 可选单位:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)、TimeUnit.MINUTES(分钟) 等

5. workQueue 任务阻塞队列
当核心线程全部忙碌时,新任务的排队容器,采用阻塞队列实现
Java 中常用的 3 种队列:

  • ArrayBlockingQueue:有界队列,必须指定容量,能有效控制任务堆积;
  • LinkedBlockingQueue:无界队列,容量默认Integer.MAX_VALUE,会导致任务无限排队,线程池永远不会创建非核心线程;
  • SynchronousQueue:同步队列,无容量,放一个任务必须立刻有线程取走,否则阻塞,适合任务量极大、要求快速执行的场景。

6. threadFactory 线程工厂
用于创建线程的工具类,给线程池创建的线程命名、设置优先级、设置是否为守护线程等。
默认使用 Executors.defaultThreadFactory(),创建的线程名格式为 pool-1-thread-1

7. RejectedExecutionHandler 拒绝策略
线程池的最后兜底方案:当线程数达到最大值 + 任务队列已满时,线程池无法处理新任务,此时就会执行拒绝策略,处理这个无法被执行的任务。

线程池的 4 种默认拒绝策略

1. AbortPolicy

  • 行为:直接抛出异常 RejectedExecutionException,中断程序执行;
  • 适用场景:生产环境最常用,因为任务无法执行时抛出异常,能及时发现问题,避免静默失败导致的业务异常。

2. CallerRunsPolicy

  • 行为:不抛出异常,也不丢弃任务,而是让提交任务的线程自己执行这个任务;
  • 适用场景:并发量小、任务重要性一般的场景,能保证任务不丢失,但会阻塞提交线程。

3. DiscardPolicy

  • 行为:直接丢弃当前任务,不抛出任何异常
  • 适用场景:不推荐,任务丢失了也无法感知,排查问题极其困难。

4. DiscardOldestPolicy

  • 行为:丢弃任务队列中排队最久的任务,把当前任务加入队列尝试排队;
  • 适用场景:任务有先后顺序、允许丢弃旧任务的场景,比如日志收集、统计上报等非核心任务。

Java 中 4常用的内置线程池

JUC 包提供了工具类 Executors,封装了常用的线程池,底层都是通过ThreadPoolExecutor实现,无需自己手动配置参数。

1. 定长线程池:Executors.newFixedThreadPool (int nThreads)

  • 核心参数:corePoolSize = maximumPoolSize = nThreads,无救急线程,队列是无界的LinkedBlockingQueue
  • 特点:线程数量固定,全部是核心线程,空闲时不销毁,任务无限排队
//创建固定3个核心线程的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

2. 单线程线程池:Executors.newSingleThreadExecutor ()

  • 核心参数:corePoolSize = maximumPoolSize = 1,只有 1 个核心线程,队列无界
  • 特点:串行执行所有任务,保证任务执行顺序,线程挂了会自动新建一个
//只有1个核心线程,任务串行执行
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

3. 缓存线程池:Executors.newCachedThreadPool ()

  • 核心参数:corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE,队列是SynchronousQueue(无容量),空闲存活时间 60 秒;
  • 特点:无核心线程,来任务就创建新线程,线程空闲 60 秒销毁,任务无排队;
//无核心线程,按需创建线程,空闲60秒销毁
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

4. 定时线程池:Executors.newScheduledThreadPool (int corePoolSize)

  • 底层是 ScheduledThreadPoolExecutor,继承ThreadPoolExecutor;
  • 特点:支持延迟执行、定时重复执行任务;
//指定2个核心线程,支持延迟+定时重复执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);

// 方式1:延迟执行(只执行1次):延迟1秒后执行任务
scheduledThreadPool.schedule(() -> {
    System.out.println("定时线程池-延迟任务执行,线程:" + Thread.currentThread().getName());
}, 1, TimeUnit.SECONDS);

// 方式2:固定频率执行(多次):初始延迟2秒,之后每隔3秒执行1次
scheduledThreadPool.scheduleAtFixedRate(() -> {
    System.out.println("线程:" + Thread.currentThread().getName());
    try { Thread.sleep(1000); } catch (Exception e) {} // 任务耗时1秒
}, 2, 3, TimeUnit.SECONDS);

// 方式3:固定延迟执行(多次):初始延迟2秒,上一次执行完后隔3秒再执行
scheduledThreadPool.scheduleWithFixedDelay(() -> {
    System.out.println("线程:" + Thread.currentThread().getName());
    try { Thread.sleep(1000); } catch (Exception e) {} // 任务耗时1秒
}, 2, 3, TimeUnit.SECONDS);

scheduleAtFixedRate和schedulWithtFixedRate区别

  1. scheduleAtFixedRate(2,3) → 固定频率执行
    以上一次任务开始执行的时间计算下一次执行时间
    scheduleAtFixedRate(2,3) 执行时间线
0秒  程序启动 → 等待初始延迟 2秒
2秒  第一次任务开始执行 → 执行耗时1秒 → 3秒执行完毕
5秒  第二次任务开始执行 → 执行耗时1秒 → 6秒执行完毕 
8秒  第三次任务开始执行 → 执行耗时1秒 →9秒执行完毕   
11秒 第四次任务开始执行 →...
  1. scheduleWithFixedDelay(2,3) → 固定延迟执行
    以上一次任务执行完毕的时间计算下一次执行时间
    scheduleWithFixedDelay(2,3)执行时间线
0秒  程序启动 → 等待初始延迟 2秒
2秒  第一次任务开始执行 → 执行耗时1秒 → 3秒执行完毕 → 开始等3秒
6秒  第二次任务开始执行 → 执行耗时1秒 →7秒执行完毕 → 开始等3秒
10秒 第三次任务开始执行 → 执行耗时1秒 →11秒执行完毕 → ...

线程池的操作

任务提交的两种方式
  1. execute(Runnable task):提交无返回值的任务,无法获取任务执行结果,底层就是线程池的核心执行方法;
  2. submit(Callable<T> task):提交有返回值的任务,返回一个Future对象,通过future.get()获取执行结果,适合需要获取任务结果的场景。
线程池的关闭方式

线程池是常驻内存的,程序结束时如果不关闭,线程池的线程会一直存活,导致进程无法退出,内存泄漏。提供 2 种关闭方式,按需选择:

  1. shutdown():温和关闭,不再接收新任务,等待队列中所有任务执行完毕后,再销毁线程;
  2. shutdownNow():强制关闭,不再接收新任务,中断正在执行的任务,清空队列,立即销毁线程,适合紧急停止场景。

Lock锁

Lock 是一个 Java 接口,全类名java.util.concurrent.locks.Lock,是一种手动锁:锁的获取、释放都需要开通过代码手动调用方法完成,和synchronized作用一致,都是为了保证多线程下共享资源的安全访问、解决线程安全问题

Lock接口核心API

// 1.获取锁,如果锁被其他线程占用,则一直阻塞等待,直到获取锁成功
void lock();

// 2.释放锁:手动释放锁,解锁成功后其他线程才能获取锁
void unlock();

// 3.尝试获取锁:非阻塞式获取锁
//返回值是boolean:true=获取锁成功  false=获取锁失败(锁被占用)
boolean tryLock();

// 4.超时尝试获取锁:带等待时间的尝试获取锁
// 在指定时间内,能获取锁就返回true;超时还没获取到,返回false;也可以被中断
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

// 5.获取可中断的锁:获取锁时,如果线程被其他线程中断,则抛出异常,停止等待锁
void lockInterruptibly() throws InterruptedException;

实现类ReentrantLock

Lock 只是接口,不能直接 new 对象,Java 提供了现成的实现类,其中 ReentrantLock 是最常用、最重要、最核心的实现类

  • 全类名:java.util.concurrent.locks.ReentrantLock
  • Reentrant = 可重入,Lock= 锁
  • 核心特点:和 synchronized 一样,都是可重入锁(一个线程可以多次获取同一个锁,不会自己锁死自己)
  • 无参构造:Lock lock = new ReentrantLock()

和synchronized区别

对比维度synchronized(同步锁)Lock(接口,常用实现类 ReentrantLock)
锁的类型隐式锁(JVM 层面的关键字)显式锁(Java 代码层面的 API)
加锁 / 释放锁自动加锁、自动释放,无需手动操作线程执行完代码 / 抛异常,JVM 自动解锁手动加锁:lock.lock()手动释放:lock.unlock(),必须写在 finally 中保证释放,否则会锁泄漏
线程等待是否可中断不可中断,拿不到锁就无限阻塞死等,无法停止等待支持可中断,通过lockInterruptibly()实现,等待锁时可被中断,停止等待
是否支持尝试获取锁不支持,要么拿到锁执行,要么阻塞等待支持 2 种尝试获取
1.tryLock():无等待,拿不到锁直接返回 false
2.tryLock(时间,单位):超时拿不到锁返回 false
公平锁 / 非公平锁只支持非公平锁都支持,创建时指定
非公平锁(默认):new ReentrantLock()
公平锁:new ReentrantLock(true)(按排队顺序拿锁)
线程唤醒方式只能配合 wait()/notify()/notifyAll()
随机唤醒一个线程 或 唤醒全部线程,无法精准唤醒指定线程
支持Condition接口
可以绑定多个条件队列,精准唤醒指定线程,不唤醒其他线程
性能JDK1.6 后 JVM 做了大量优化,性能和 Lock 持平JDK1.5 性能远超 synchronized,JDK1.6 后两者性能基本一致
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐