Java并发基础知识

线程 

什么是程序

程序=算法+数据结构+编程语言

静态的没有运行的。

什么是进程

进程是程序执行的实例,在一个操作系统中,正在运行的程序就是进程

什么是线程

在一个进程中,正在运行的子程序流就是线程。

是程序执行的最小单元,CPU调度的最小单位,指的是一个进程内的一个独立的执行路径。

进程是线程的集合,一个进程除了包含多个进程之外还包含其余的共享变量空间。

线程和CPU之间的关系

  • 线程执行:线程的指令由 CPU 执行。CPU 是实际运行线程代码的硬件。

  • 调度与上下文切换:操作系统负责调度线程在 CPU 上执行,通过上下文切换在不同线程之间切换 CPU 时间。

  • 多核并行:多核 CPU 可以同时运行多个线程,实现真正的并行处理。单核 CPU 通过快速切换线程实现并发。

  • 资源共享:线程在同一进程内共享内存等资源,但每个线程需要自己的寄存器和栈。

  • 线程管理:线程的创建、调度和销毁都是由操作系统的调度器和 CPU 协同管理的。

注意:同一时刻一个CPU只会执行一个线程的任务

什么是上下文切换

上下文切换(Context Switch)是指当操作系统在不同的线程或进程之间切换执行时,保存当前线程或进程的状态(即上下文),并恢复下一个要运行的线程或进程的状态的过程。上下文切换使得操作系统能够在多个进程或线程之间分配 CPU 资源,实现并发和多任务处理。

上下文(Context)是什么?

上下文指的是当前进程或线程的运行状态,主要包括以下信息:

  1. 程序计数器(PC):当前正在执行的指令地址。
  2. CPU 寄存器:包括通用寄存器、栈指针、基址指针等,存储着当前计算的临时数据。
  3. 内存状态:虚拟内存映射、页面表、堆栈等。
  4. 程序状态字(PSW):包含了当前的 CPU 状态、优先级、中断标志等。
  • 进程上下文切换

    • 在进程之间切换时,操作系统不仅要保存和恢复 CPU 寄存器程序计数器,还要切换内存映射、页面表等。这是因为不同进程有各自独立的地址空间。
    • 进程的上下文切换开销较大,通常涉及到内核态的复杂操作,比如更新虚拟内存地址映射。
  • 线程上下文切换

    • 在线程之间切换时,线程共享同一个进程的地址空间,因此上下文切换只需要保存和恢复 CPU 寄存器和程序计数器,无需切换内存映射等资源。
    • 线程的上下文切换相对开销较小

在高并发或多任务场景中,频繁的上下文切换可能会导致过多的开销,从而降低系统性能。因此,减少不必要的上下文切换是系统性能优化的重要方面。

用户态和内核态

  • 内核态是操作系统的核心运行模式,具有最高权限,用于执行系统级功能和管理硬件资源。用户程序通过系统调用进入内核态来请求系统服务。
  • 用户态是应用程序的运行模式,权限较低,不能直接访问系统资源。用户态代码通过系统调用与内核交互,实现对硬件的间接访问。

这两种模式的分离有助于提高系统的稳定性和安全性,同时使操作系统能够有效管理和隔离不同程序的运行。

创建线程的方法 

  • 继承 Thread:重写 run(),通过 start() 启动线程。
  • 实现 Runnable 接口:适用于多继承场景,任务执行放在 run() 中。
  • 匿名内部类或 Lambda 表达式:简化线程的创建和启动。
  • 实现 Callable 接口:用于返回结果的线程,适合需要返回值或处理异常的任务。

线程的生命周期

  1. 新建(new):线程被创建,但未开始运行。
  2. 就绪(Runnable):线程已准备好运行,等待 CPU 调度。
  3. 运行(Running):线程正在执行任务。
  4. 阻塞(Blocked):线程等待资源或事件,无法继续执行。
  5. 等待(Waiting):线程主动让出 CPU,等待其他线程唤醒。
  6. 超时等待(Timed Waiting):线程等待指定时间,超时后进入就绪状态。
  7. 终止(Dead):线程完成任务或遇到异常,生命周期结束。

 锁机制

锁的优化机制

从JDK 1.6 版本之后,synchronized本身也在不断优化锁的机制,有些情况下他并不是一个很重量级的的锁了。
优化机制包括自适应锁、自旋锁、锁消除、锁粗轻量级锁和偏向锁。锁的状态从低到高,依次为无锁 - 偏向所 - 轻量级锁 - 重量级锁,升级的过程就是从低到高,降级在一定条件也是有可能发生的。

自旋锁: 由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置-XX:+UseSpining来开启,自旋的默认次数是10次,可以使用-XX:PreBlockSpin设置。

自适应锁: 自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上的自旋时间和锁的持有者状态来决定。

锁消除: 锁消除指的是JVM检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要加锁,就会进行锁消除。

锁粗化: 锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操作序列之外。

偏向锁: 当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程ID,之后这个线程再次进入同步块时都不需要CAS来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用过设置-XX:+UseBiasedLocking开启偏向锁。

轻量级锁: JVM的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,JVM将会使用CAS方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,当前线程就尝试自旋来获得锁。

syncronized

synchronized 是 Java 中最基础的锁机制,提供内置的互斥锁,保证同一时间只有一个线程可以访问被锁住的代码块或方法。

用法

  • 同步方法:在方法上加 synchronized,表示该方法是同步方法,同一时刻只能有一个线程执行。
  • 同步代码块:使用 synchronized 块来锁定某个对象,确保只有一个线程能执行块内的代码。
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

特点

  • 阻塞机制:其他线程试图访问同步代码时会被阻塞,直到锁被释放。
  • 可重入锁:一个线程可以多次进入被同一个 synchronized 锁住的代码块,不会发生死锁。

 

ReentrantLock(可重入锁)

ReentrantLock 是 Java java.util.concurrent.locks 包中的类,提供了比 synchronized 更灵活的锁机制。它是显式锁,可以手动获取和释放锁。

用法:

import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private int count = 0;
    private ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();  // 获取锁
        try {
            count++;
        } finally {
            lock.unlock();  // 确保释放锁
        }
    }

    public int getCount() {
        return count;
    }
}

特点:

  • 显式锁控制:需要手动获取和释放锁,灵活性更高。
  • 可重入:与 synchronized 一样,一个线程可以多次获得同一把锁。
  • 公平性ReentrantLock 可以设置为公平锁(公平性参数 true),保证锁按请求的顺序被获取。
  • 提供条件变量:通过 newCondition() 可以创建多个条件变量,精确控制线程的等待与通知机制。

悲观锁和乐观锁

悲观锁和乐观锁是两种不同的并发控制策略,用于解决多线程或多进程环境下对共享资源的访问冲突。它们的区别在于对数据冲突发生的假设和处理方式

悲观锁

悲观锁假设数据在并发操作时每次都会发生冲突,因此在对获取资源操作之前,直接锁定资源,保证在锁释放之前,其他线程或进程无法访问该资源。想法非常悲观所以叫悲观锁

实现方法

在 Java 并发编程中,synchronized ReentrantLock 都是悲观锁的实现。它们在访问共享资源时,会加锁,其他线程在锁释放前无法访问。

乐观锁

与悲观锁相反,乐观锁假设每次执行都不会发生冲突,不需要加锁,每次获取资源之前,只需要用版本号机制CAS算法去检测对应资源有没有被其他资源修改即可。

实现方法

在 Java 中,AtomicIntegerAtomicReference 等类采用了乐观锁的思想,内部使用了CAS(Compare And Swap,比较并交换) 操作。CAS 是一种无锁操作,通过比较当前值和期望值是否一致来决定是否更新数据。

CAS(Compare And Swap,比较并交换)

  • 读取变量的当前值。
  • 比较当前值是否等于期望值。
  • 如果相等,更新为新值;否则,不做修改并重试。

区别

自旋锁4

线程池

是提前创建好的、可以重复使用的线程集合。当有任务需要执行时,线程池会提供一个线程执行任务,任务完成后,线程不会被销毁,而是返回线程池等待执行下一个任务。

核心优势

  • 减少线程创建和销毁的开销:线程的创建和销毁代价较大,线程池通过复用线程来降低开销。
  • 控制并发线程数:通过设置线程池的大小,限制同时运行的线程数,避免系统过载。
  • 任务管理:可以对任务进行排队、调度和管理,避免大量任务同时执行导致资源耗尽。

如何创建线程池 

Java 提供了 java.util.concurrent 包来支持线程池,推荐通过ThreadPoolExecutor构造函数来创建。

ThreadPoolExecutor 是 Java 中高度可配置的线程池实现类,提供了灵活的参数配置来控制线程池的行为。通过 ThreadPoolExecutor 的构造函数,可以精细化地创建线程池,设置核心线程数、最大线程数、任务队列以及拒绝策略等。

核心参数

  • corePoolSize(核心线程数)

    • 定义了线程池中最小的线程数量,线程池会始终保持至少有 corePoolSize 个线程存在,即使这些线程是空闲的。
    • 当提交任务时,如果当前线程数少于核心线程数,则会创建新的线程执行任务,而不将任务放入队列中。
  • maximumPoolSize(最大线程数)

    • 定义了线程池中允许存在的最大线程数。当任务队列已满时,线程池会继续创建新线程,直到达到 maximumPoolSize
  • keepAliveTime(线程空闲存活时间)

    • 当线程池中的线程数量超过 corePoolSize 时,多余的线程在空闲超过 keepAliveTime 时间后会被回收。
    • 如果线程池中的线程数不超过核心线程数,则这些线程不会被回收。
  • unit(时间单位)

    • 定义了 keepAliveTime 的时间单位,如 TimeUnit.SECONDSTimeUnit.MILLISECONDS 等。
  • workQueue(任务队列)

    • 当线程池中的线程数达到 corePoolSize 时,新提交的任务会被放入任务队列等待执行
    • 常见的任务队列实现:
      • LinkedBlockingQueue:基于链表的无界队列。
      • ArrayBlockingQueue:基于数组的有界队列。
      • SynchronousQueue:不存储任务的队列,任务直接提交给线程执行。
  • threadFactory(线程工厂)

    • 用于创建线程的工厂,通常用于为线程命名或设置守护线程。
    • ThreadFactory 接口允许自定义线程的创建逻辑,Java 提供了默认实现 Executors.defaultThreadFactory()
  • handler(拒绝策略)

    • 当线程池的线程数达到 maximumPoolSize,且任务队列也已满时,新提交的任务将被拒绝执行,线程池会根据指定的 RejectedExecutionHandler 拒绝策略来处理这些任务。
    • 常见的拒绝策略:
      • AbortPolicy(默认):抛出 RejectedExecutionException 异常。
      • DiscardPolicy:直接丢弃任务,不抛异常。
      • DiscardOldestPolicy:丢弃队列中等待最久的任务,然后尝试重新提交当前任务。
      • CallerRunsPolicy:由调用线程(提交任务的线程)来执行任务。

线程池的工作流程 

  1. 任务提交:任务被提交到线程池后,首先判断线程池中运行的线程数是否小于核心线程数(corePoolSize,如果小于则创建新线程执行任务。
  2. 任务排队:如果线程数达到核心线程数,任务被放入等待队列中排队。
  3. 扩展线程池:如果任务排队已满,并且线程数还未达到最大线程数maximumPoolSize),则创建新的线程执行任务。
  4. 拒绝任务:如果线程池中的线程数达到最大线程数,并且任务队列也已满,线程池会根据设置的拒绝策略来处理新提交的任务。
  5. 回收空闲线程:如果线程池中的线程数大于核心线程数,且某些线程长时间没有任务执行,这些空闲线程会在超过 keepAliveTime 后被终止。

总结

  • 线程池是一种用于管理线程资源的机制,能够提升性能,减少系统开销。
  • Java 提供了多种线程池类型,如 FixedThreadPoolCachedThreadPoolScheduledThreadPool 等,以适应不同的应用场景。
  • ThreadPoolExecutor 是 Java 线程池的核心实现类,具有高度的可配置性,可以通过核心线程数、最大线程数、任务队列和拒绝策略等参数进行灵活配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值