图文详解Java并发面试题

文章目录

  • 1、并发与并行
  • 2、线程安全
  • 3、线程、进程、协程
  • 4、线程间通信
  • 5、线程创建方式
  • 6、8G内存创建的线程数
  • 7、普通Java程序含有的线程
  • 8、start()、run()
  • 9、线程调度、6种状态、强制停止线程、上下文切换
  • 10、守护线程、用户线程
  • 11、 volatile 、synchronized
  • 12、sleep() 、 wait()
  • 13、线程安全、场景
  • 14、ThreadLocal
  • 15、内存模型、指令重排
  • 16、Happens-Before、As-If-Serial
  • 17、volatile
  • 18、synchronized锁升级、Monitor
  • 19、synchronized 、 ReentrantLock、Lock、AQS
  • 20、非公平锁、公平锁
  • 21、CAS
  • 22、原子操作类
  • 23、线程死锁
  • 24、线程同步、互斥、锁要解决的问题、自旋锁
  • 25、悲观锁、乐观锁
  • 26、并发工具类:CountDownLatch
  • 27、并发工具类:CyclicBarrier
  • 27、并发工具类:Semaphore
  • 28、并发工具类:Exchanger
  • 29、ConcurrentHashMap
  • 30、CopyOnWriteArrayList
  • 30、BlockingQueue
  • 31、线程池

1、并发与并行

①并行:多核CPU上的多任务处理,多个任务在同一时间真正地同时执行。②并发:单核CPU上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行,解决IO密集型任务的瓶颈。

在这里插入图片描述

2、线程安全

①原子性:一个操作要么完全执行,要么完全不执行,不会出现中间状态。措施:atomic包和synchronized。
②可见性:当一个线程修改了共享变量,其他线程能够立即看到变化。措施:volatile关键字。
③有序性:确保线程不会因为死锁、饥饿导致无法继续执行。措施:读写屏障和禁止指令重排。

3、线程、进程、协程

①进程:操作系统资源分配的基本单位,有独立的资源。
②线程:独立执行的单元,共享进程资源,有独立的栈和寄存器。
③协程:比线程更轻量的并发单元、在单线程中并发执行,在用户态显式调度,避免线程切换时的内核态开销。

在这里插入图片描述

4、线程间通信

①共享内存:Java采用,JMM(抽象概念),共享变量存储在主内存中,每个线程私有本地内存存储共享变量的副本。②具体实现:i:volatile和synchronized关键字共享对象。ii:wait()和notify():wait()进入该对象等待池,释放锁; notify()唤醒对象等待池中一个线程进入锁池,等待获取锁。iii:Exchanger数据交换:一个线程调用exchange()方法,将数据传递给另一个线程,同时接收另一个线程数据。iv:Condition实现线程间的协调:await()负责阻塞、signal()和signalAll()负责通知。v:CompletableFuture:支持异步编程,允许线程在完成计算后将结果传递给其他线程。例子如下:

在这里插入图片描述

线程A与线程B之间通信2个步骤:①线程A把本地内存A中的共享变量副本刷新到主内存中。
②线程B到主内存中读取线程A刷新过的共享变量,再同步到自己的共享变量副本中。

在这里插入图片描述

5、线程创建方式

在这里插入图片描述

①继承Thread类:重写父类Thread的run()方法,并且调用start()方法启动线程。
class ThreadTask extends Thread {public void run() {System.out.println("上岸!");}public static void main(String[] args) {ThreadTask task = new ThreadTask();task.start();}
}
缺点:如果ThreadTask已经继承另外一个类,不能再继承Thread类,因为Java不支持多重继承。
②实现Runnable接口:重写Runnable接口run方法,并将实现类的对象作为参数传递给Thread对象构造方法,调用start方法启动线程。
class RunnableTask implements Runnable {public void run() {System.out.println("上岸!");}public static void main(String[] args) {RunnableTask task = new RunnableTask();Thread thread = new Thread(task);//实现类的对象作为参数传递给 Thread 对象构造方法thread.start();}
}
优点:避免Java的单继承限制
③实现Callable接口:重写Callable接口的call()方法,创建FutureTask对象,参数为Callable实现类的对象;
再创建Thread对象,参数为FutureTask对象,最后调用start()方法启动线程。
class CallableTask implements Callable<String> {public String call() {return "上岸!";}public static void main(String[] args) throws ExecutionException, InterruptedException {CallableTask task = new CallableTask();FutureTask<String> futureTask = new FutureTask<>(task);Thread thread = new Thread(futureTask);thread.start();System.out.println(futureTask.get());}
}
优点:获取线程的执行结果。

6、8G内存创建的线程数

①理论上:8000个,在64位OS中,创建线程会分配虚拟机栈,JVM默认是1024KB=1M。②实际上:JVM、操作系统本身运行就要占一定内存空间,实际上创建线程数远比8000少。
ThreadStackSize的单位是KB,默认的JVM栈大小。

在这里插入图片描述

7、普通Java程序含有的线程

①main 线程:程序执行入口。
②垃圾回收线程:一个后台线程,负责回收不再使用的对象。
③编译器线程:如JIT,负责把一部分热点代码编译后放到codeCache中。例子:
class ThreadLister {public static void main(String[] args) {// 获取所有线程的堆栈跟踪Map<Thread, StackTraceElement[]> threads = Thread.getAllStackTraces();for (Thread thread : threads.keySet()) {System.out.println("Thread: " + thread.getName() + " (ID=" + thread.getId() + ")");}}
}
输出结果分析如下:
Thread: main (ID=1) - :主线程,Java 程序启动时由 JVM 创建。
Thread: Reference Handler (ID=2) - :处理引用对象的,如软引用、弱引用和虚引用。负责清理被 JVM 回收对象。
Thread: Finalizer (ID=3) - :终结器线程,调用对象finalize 方法,执行特定的资源释放操作。
Thread: Signal Dispatcher (ID=4) - :信号调度线程,处理来自操作系统信号给 JVM 处理,如响应中断、停止等信号。
Thread: Monitor Ctrl-Break (ID=5) - :监视器线程,由一些特定的 IDE 创建,监控和管理程序执行或者处理中断。

8、start()、run()

①start() :创建一个新线程,并异步执行run()中代码,通知JVM调用底层线程调度机制来启动新线程。
②run() :一个普通同步方法调用,所有代码都在当前线程中执行,不会创建新线程。
class MyThread extends Thread {public void run() {System.out.println(Thread.currentThread().getName());}public static void main(String[] args) {MyThread t1 = new MyThread();t1.start(); // 创建一个新线程,并在新线程中执行 run()。输出maint1.run(); // 仅在主线程中执行 run(),没有创建新线程。输出Thread-0}
}原理如下:
调用start()后,线程进入就绪状态,等待操作系统调度;一旦调度执行,线程会执行其run()方法中的代码。

在这里插入图片描述

9、线程调度、6种状态、强制停止线程、上下文切换

线程调度方法:
①start :启动线程并让操作系统调度执行。
②sleep :让当前线程休眠一段时间,暂时让出指定时间的执行权。时间到接着参与CPU调度,获取CPU资源后继续执行。
③yield:让当前线程让出CPU使用权,回到就绪状态。
④wait :让当前线程等待。
⑤notify :唤醒一个等待的线程。
⑥stop :强制停止线程,目前已经处于废弃状态。
⑦interrupt:通知线程停止,但不会直接终止线程,线程自行处理中断标志。配合isInterrupted或 Thread.interrupted使用。

在这里插入图片描述

线程6种状态:
①new:线程被创建但未启动,已分配必要的资源。
②runnable :线程处于正在运行状态,由操作系统调度。
③blocked :获取一个锁以进入同步块/方法时,线程被阻塞,等待获取锁。
④waiting :线程等待其他线程的通知或中断。
⑤timed_waiting :线程会等待一段时间,超时后自动恢复。
⑥terminated :线程执行完毕,生命周期结束。线程生命周期五个主要阶段:新建、就绪、运行、阻塞和终止。

在这里插入图片描述

强制终止线程:
①调用线程的 interrupt() 方法,请求终止线程。
②在线程的 run() 方法中检查中断状态,如果线程被中断,就退出线程。
线程上下文切换: 
定义:CPU 从一个线程切换到另一个线程执行时的过程。过程:
①在线程切换的过程中,CPU 需要保存当前线程的执行状态,并加载下一个线程的上下文。
②CPU 在同一时刻只能执行一个线程,为了实现多线程并发执行,采用时间片轮转在多个线程之间切换。

在这里插入图片描述

10、守护线程、用户线程

①作用:一种特殊线程,为其他线程提供服务。Java线程分为用户线程和守护线程。②区别:当最后一个非守护线程束时,JVM会正常退出,不管当前是否存在守护线程,守护线程是否结束并不影响 JVM 退出。

11、 volatile 、synchronized

①volatile :修饰成员变量,对变量访问均从共享内存中获取,并同步刷新回共享内存,保证所有线程对变量访问的可见性;禁止指令重排。②synchronized:修饰方法或同步代码块,确保多个线程在同一个时刻只有一个线程在执行方法或代码块。

12、sleep() 、 wait()

①sleep() :让当前线程休眠,属于Thread 类方法,不释放锁;在任何地方被调用;可指定的时间内暂停执行。②wait():让获得对象锁的线程等待,属于Object类方法,释放锁;只使用在同步代码块或同步方法中;配合notify或notifyAll使用。

13、线程安全、场景

①线程安全:在并发环境下,多个线程访问共享资源时,程序能够正确地执行,而不会出现数据不一致的问题。②措施:i:synchronized 关键字:对方法、代码块加锁。线程在执行同步方法、同步代码块时,获取锁,其他线程阻塞并等待锁。ii:ReentrantLock 并发重入锁,更细粒度的锁。iii:变量内存可见性,使用 volatile 关键字。iv:简单原子变量操作,使用 Atomic 原子类。v:对于线程独立的数据,使用 ThreadLocal 来为每个线程提供专属的变量副本。vi:对于需要并发容器的地方,使用 ConcurrentHashMap、CopyOnWriteArrayList 等。
例子:
Q:int变量初始为0,十个线程轮流对其进行++操作(循环10000次),结果大于10 万还是小于等于10万,为什么?
A:小于 100000,原因是多线程环境下,++ 操作并不是一个原子操作,而是分为读取、加 1、写回三个步骤。i:读取变量的值。ii:将读取到的值加 1。iii:将结果写回变量。多个线程读取到相同的值,然后对这个值进行加 1 操作,最终导致结果小于 100000。
线程安全的使用场景:单例模式。在多线程环境下,如果多个线程同时尝试创建实例,
单例类必须确保只创建一个实例,并提供一个全局访问点。
class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
饿汉式单例:一种比较直接的实现方式,通过在类加载时就立即初始化单例对象来保证线程安全。
class LazySingleton {private static volatile LazySingleton instance;private LazySingleton() {}public static LazySingleton getInstance() {if (instance == null) { // 第一次检查synchronized (LazySingleton.class) {if (instance == null) { // 第二次检查instance = new LazySingleton();}}}return instance;}
}懒汉式单例:第一次使用时初始化单例对象,使用双重检查锁定来确保线程安全,
volatile 关键字用来保证可见性,syncronized 关键字用来保证同步。

14、ThreadLocal

①特点:线程局部变量的工具类,每个线程访问变量副本独立,避免共享变量引起的线程安全问题,使得变量不需要同步处理,避免资源竞争。②使用ThreadLocal 分为四步:
//1、创建一个ThreadLocal变量
public static ThreadLocal<String> localVariable = new ThreadLocal<>();
//2、设置ThreadLocal变量的值
localVariable.set("毕业");
//3、获取ThreadLocal变量的值
String value = localVariable.get();
//4、删除ThreadLocal变量的值
localVariable.remove();

在这里插入图片描述

③底层原理:i:ThreadLocal对象并调用set方法,每个线程初始化维护一个ThreadLocalMap,通过set方法将对象存入Map。
ThreadLocalMap内部维护一个 Entry 数组,key是ThreadLocal对象,value是线程局部变量。Entry继承WeakReference,限定key是弱引用,弱引用好处是当内存不足时,JVM会回收ThreadLocal对象,将其对应Entry.value设置为null,很大程度上避免内存泄漏。ii:通过ThreadLocal的get方法从Map中取出对象。iii:Map的大小由ThreadLocal对象的多少决定。

在这里插入图片描述

④内存泄漏:i原因:ThreadLocalMap的Key是弱引用,Value是强引用。线程运行且value指向某个强引用对象,该对象不会被回收,导致内存泄漏。ii方法:用完ThreadLocal后,及时调用remove()释放内存空间,全部清除所有key为null的Entry。
⑤ThreadLocalMap源码:i:没实现Map接口,是一个简单的线性探测哈希表。设计目的是存储线程私有数据,不会有大量Key,采用线性探测更节省空间。
底层是数组,数组元素是Entry对象,Entry对象继承WeakReference,key是ThreadLocal对象,value是局部变量。ii:继承实现子线程不会继承父线程的 ThreadLocalMap,可使用 InheritableThreadLocal实现父线程用ThreadLocal给子线程传值。原因:每个线程都有两个ThreadLocalMap:ThreadLocal变量存储在threadLocals中,不会被子线程继承。InheritableThreadLocal变量存储在inheritableThreadLocals中,当new Thread()创建一个子线程时,Thread的init()会检查父线程是否有inheritableThreadLocals,如果有,拷贝InheritableThreadLocal变量到子线程。iii:扩容机制采用“先清理再扩容”策略,threshold默认值是数组长度三分之二,扩容时数组长度变为原来的2倍,并重新计算索引,如果发生哈希冲突,采用线性探测法来解决。
}

在这里插入图片描述

15、内存模型、指令重排

①内存模型:i:Java 虚拟机规范中定义的一个抽象模型,用来描述多线程环境中共享变量的内存可见性。ii:线程从主内存拷贝变量到工作内存,减少CPU访问RAM开销。每个线程都有变量副本,避免多个线程同时修改共享变量导致的数据冲突。iii:原子性保证操作不可中断,可见性保证变量修改后线程能看到最新值,有序性保证代码执行顺序一致,通过volatile、synchronized实现。

在这里插入图片描述

②指令重排:i:CPU或编译器为提高程序的执行效率,改变代码执行顺序的一种优化技术。ii:从Java源代码到最终执行的指令序列,会经历3种重排序:编译器重排序、指令并行重排序、内存系统重排序。

在这里插入图片描述

  iii:重排可能会导致双重检查锁失效。
class Singleton {private static volatile Singleton instance;//单例模式使用volatile禁止指令重排public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton(); // 由于 volatile,禁止指令重排}}}return instance;}}

在这里插入图片描述

16、Happens-Before、As-If-Serial

①Happens-Before:Java内存模型定义的一种保证线程间可见性和有序性的规则。
如果操作A Happens-Before操作B:操作A的结果对操作B可见;操作A在时间上先于操作B执行,并且B不能重排序到A之前。JMM的Happens-Before原则,如下保证可见性:i:程序顺序规则:单线程内,代码按顺序执行;比如 a = 1; b = 2;,a 先于 b 执行。ii:监视器锁定规则:unlock() Happens-Before lock();比如synchronized释放锁后,获取锁的线程能够看到最新的数据。iii:volatile变量规则:写volatile变量Happens-Before读volatile。iv:传递性规则:A Happens-Before B 且 B Happens-Before C,则A Happens-Before C。v:线程启动规则:线程A执行操作ThreadB.start(),A线程的ThreadB.start()操作happens-before于线程B中的任意操作。vi:线程终止规则:线程的所有操作Happens-Before Thread.join();例如t.join()之后,主线程一定能看到t的修改。

在这里插入图片描述

②As-If-Serial规则:i:允许CPU和编译器优化代码顺序,但不会改变单线程的执行结果。ii:只适用于单线程,多线程环境仍然可能发生指令重排,需要volatile和synchronized等机制来保证有序性。

17、volatile

①保证可见性,线程修改volatile修饰的变量后,其他线程能够立即看到最新值。
②防止指令重排,volatile修饰的变量写入不会被重排序到它之前的代码。③JVM会在volatile变量的读写前后插入“内存屏障”,以约束CPU和编译器的优化行为:i:StoreStore屏障禁止普通写操作与volatile写操作的重排。ii:StoreLoad屏障会禁止volatile写与volatile读重排。iii:LoadLoad屏障会禁止volatile读与后续普通读操作重排。iv:LoadStore屏障会禁止volatile读与后续普通写操作重。

在这里插入图片描述

18、synchronized锁升级、Monitor

①synchronized:i特点:依赖JVM内部的Monitor对象来实现线程同步,不用手动去lock和unlock,JVM会自动加锁和解锁。ii原理:synchronized 加锁代码块时,JVM 会通过monitorenter、monitorexit指令来实现同步,禁止指令重排。②synchronized支持可重入:i:底层通过Monitor对象owner和count字段实现的,owner记录持有锁线程,count记录线程获取锁次数。ii:Java对象头包含一个Mark Word存储对象的状态,包括锁信息。当一个线程获取对象锁时,JVM会将该线程的ID写入Mark Word,并将锁计数器设为1。如果一个线程尝试再次获取已经持有的锁,JVM 会检查Mark Word中的线程ID。iii:如果ID匹配表示同一个线程,锁计数器递增。iv:当线程退出同步块时,锁计数器递减。如果计数器值为零,JVM 将锁标记为未持有状态,并清除线程ID信息。可重入:同一个线程可以多次获得同一个锁,而不会被阻塞。
③Monitor:i:JVM内置的同步机制,每个对象在内存中都有一个对象头Mark Word,用于存储锁的状态,以及Monitor对象的指针。Monitor结构如下:ii:Synchronized依赖对象头的Mark Word进行状态管理,支持无锁、偏向锁、轻量级锁,以及重量级锁。Hotspot 虚拟机中,Monitor由ObjectMonitor实现:
ObjectMonitor() {_count     = 0; // 记录线程获取锁的次数(可重入锁),每次成功加锁 _count + 1,释放锁 _count - 1。_owner = NULL;  //指向持有ObjectMonitor对象的线程,null表示没有线程持有锁。线程成功获取锁后更新为线程ID,释放锁后为null。_WaitSet = NULL;  // 等待队列,调用 wait()线程会释放锁,并加入 _WaitSet,进入WAITING状态,等待notify() 唤醒。_cxq       = NULL ;//阻塞队列,用于存放刚进入 Monitor 的线程(还未进入 _EntryList)。_EntryList = NULL ;  // 竞争队列,所有等待获取锁的线程(BLOCKED 状态)会进入 _EntryList,等待锁释放后竞争执行权。  }

在这里插入图片描述

④synchronized保证可见性两步操作:i:加锁时,线程必须从主内存读取最新数据。ii:释放锁时,线程必须将修改的数据刷回主内存,其他线程获取锁后看到最新数据。
⑤synchronized锁升级:
JDK1.6的时候,为提升synchronized的性能,引入锁升级机制,从低开销的锁逐步升级到高开销的锁,以最大程度减少锁的竞争。i:没有线程竞争时,就使用低开销的“偏向锁”,没有额外的CAS操作;ii:轻度竞争时,使用“轻量级锁”,采用CAS自旋,避免线程阻塞;iii:只有在重度竞争时才使用“重量级锁”,由Monitor机制实现,需要线程阻塞,依赖于操作系统互斥量mutex来实现,会牵扯到os层面;mutex用于保证任何给定时间内,只有一个线程能执行某一段特定的代码段。⑥synchronized锁状态:i:偏向锁:一个线程第一次获取锁时,JVM会在对象头Mark Word记录线程ID,下次进入,如果是同一个线程直接执行,无需额外加锁。ii:轻量级锁:当多个线程尝试获取锁但不是同一个时段,偏向锁会升级为轻量级锁,等待锁的线程通过CAS自旋避免进入阻塞状态。iii:重量级锁:如果自旋失败,锁会升级为重量级锁,等待锁的线程会进入阻塞状态,等待监视器Monitor进行调度。

在这里插入图片描述

19、synchronized 、 ReentrantLock、Lock、AQS

在这里插入图片描述

①synchronized:
由JVM内部的Monitor机制实现, 自动加锁和解锁,在方法和代码块上加锁。②ReentrantLock:i:基于AQS实现,需要手动lock()和unlock(),只能在代码块上加锁,能指定公平锁还是非公平锁,提供一种能够中断等待锁的线程机制,通过lock.lockInterruptibly()来实现。ii:ReentrantLock的lock()方法的实现由ReentrantLock内部的Sync类来实现,涉及到线程的自旋、阻塞队列、CAS、AQS。iii:创建ReentrantLock的时候,传递参数 true 实现公平锁,默认是非公平锁。Q:并发量大的情况下,使用synchronized还是ReentrantLock?
A:倾向于ReentrantLock,原因:ReentrantLock提供超时和公平锁等特性,应对更复杂的并发场景。ReentrantLock允许更细粒度的锁控制,能有效减少锁竞争。ReentrantLock支持条件变量Condition,实现比synchronized更友好的线程间通信机制。
③Lock:
是JUC中的一个接口,最常用实现类包括可重入锁ReentrantLock、读写锁ReentrantReadWriteLock等。
Lock方法会首先尝试通过CAS来获取锁。如果当前锁没有被持有,将锁状态设置为1,表示锁已被占用。否则将当前线程加入AQS等待队列。
④AQS:
一个抽象类,维护一个共享变量state和一个线程等待队列,为ReentrantLock等类提供底层支持。
维护CLH 队列来维护等待线程,CLH 是三个作者Craig、Landin和Hagersten的首字母缩写,是一种基于链表的自旋锁。i:基本思想:如果被请求的共享资源处于空闲状态,则当前线程成功获取锁;否则,将当前线程加入到等待队列中,当其他线程释放锁时,
从等待队列中挑选一个线程,把锁分配给它。ii:源码:a:状态state由volatile变量修饰,保证多线程之间的可见性。b:同步队列由内部定义的Node类实现,每个Node包含等待状态、前后节点、线程的引用等,是一个先进先出的双向链表。iii:AQS两种同步方式:a:独占模式下:每次只能有一个线程持有锁,例如ReentrantLock。b:共享模式下:多个线程同时获取锁,例如Semaphore和CountDownLatch。iv:AQS核心方法包括:a:acquire:获取锁,失败进入等待队列。b:release:释放锁,唤醒等待队列中的线程。c:acquireShared:共享模式获取锁。d:releaseShared:共享模式释放锁。

在这里插入图片描述

20、非公平锁、公平锁

①公平锁:在多个线程竞争锁时,获取锁的顺序与线程请求锁的顺序相同,即先来先服务。i:公平锁的核心逻辑在AQS的hasQueuedPredecessors()方法中,该方法用于判断当前线程前面是否有等待的线程。如果队列前面有等待线程,当前线程不能抢占锁,按照队列顺序排队。如果队列前面没有线程或当前线程是队列头部线程,获取锁。②非公平锁:不保证线程获取锁的顺序,当锁被释放时,任何请求锁的线程都有机会获取锁,而不是按照请求的顺序。

在这里插入图片描述

21、CAS

①定义:CAS是一种乐观锁,比较一个变量的当前值是否等于预期值,如果相等,则更新值,否则重试。
在CAS 中,有三个值:
V:要更新的变量(var)
E:预期值(expected)
N:新值(new)②过程:i:先判断V是否等于E,如果等于,将V的值设置为N;如果不等,说明已经有其它线程更新V,当前线程就放弃更新。ii:比较和替换的操作需要是原子的,不可中断的。Java中的CAS是由Unsafe类实现。③原子性:
CPU发出一个LOCK指令进行总线锁定,阻止其他处理器对内存地址进行操作,直到当前指令执行完成。
④CAS存在三个经典问题:ABA问题、自旋开销大、只能操作一个变量。
i:ABA问题:一个值原来是A,后来被改为B,再后来又被改回A,这时CAS会误认为值没有发生变化。措施:版本号/时间戳。
class OptimisticLockExample {private int version;private int value;public synchronized boolean updateValue(int newValue, int currentVersion) {if (this.version == currentVersion) {this.value = newValue;this.version++;return true;}return false;}
}
ii:自旋开销大:失败时会不断自旋重试,如果一直不成功,会给CPU带来执行开销。措施:限制自旋次数。
int MAX_RETRIES = 10;
int retries = 0;
while (!atomicInt.compareAndSet(expect, update)) {retries++;if (retries > MAX_RETRIES) {synchronized (this) { // 超过次数,使用 synchronized 处理if (atomicInt.get() == expect) {atomicInt.set(update);}}break;}}iii:多个变量同时更:将多个变量封装为一个对象,使用AtomicReference进行CAS更新。

22、原子操作类

①定义:原子操作类基于CAS+volatile实现的,底层依赖于Unsafe类,最常用的有AtomicInteger、AtomicLong、AtomicReference。②AtomicInteger基于volatile+CAS实现,底层依赖于Unsafe类。核心方法包括 getAndIncrement、compareAndSet。

在这里插入图片描述

23、线程死锁

死锁发生在多个线程相互等待对方释放锁。
①发生死锁四个条件:i:互斥:资源不能被多个线程共享,一次只能由一个线程使用。ii:持有并等待:一个线程已经持有一个资源,并且在等待获取其他线程持有的资源。iii:不可抢占:资源不能被强制从线程中夺走,必须等线程自己释放。iv:循环等待:存在一种线程等待链,线程A等待线程B持有的资源,线程B等待线程C持有的资源,直到线程N又等待线程A持有的资源。②死锁排查:i:系统级别上排查,在Linux生产环境中,先使用top ps命令查看进程状态,是否有进程占用过多资源。ii:使用JDK自带性能监控工具排查,使用jps -l查看当前进程,然后使用jstack 进程号 ,查看进程的线程堆栈信息、线程是否等待锁资源。iii:使用一些可视化的性能监控工具,JConsole、VisualVM查看线程的运行状态、锁的竞争情况。

在这里插入图片描述

24、线程同步、互斥、锁要解决的问题、自旋锁

同步关注的是线程之间的协作,互斥关注的是线程之间的竞争。
①同步:线程之间要密切合作,按照一定的顺序来执行任务。线程A先执行,线程B再执行。②互斥:线程之间要抢占资源,同一时间只能有一个线程访问共享资源。线程A在访问共享资源时,线程B不能访问。i:使用synchronized关键字或者Lock接口的实现类,如 ReentrantLock来给资源加锁。ii:锁在操作系统层面使用Mutex,某个线程进入临界区后,其他线程不能再进入临界区,要阻塞等待持有锁的线程离开临界区。
③锁要解决哪些问题?i:谁可以拿到锁,可以是类对象,当前的this对象、任何其他新建的对象。ii:抢占锁的规则,能不能抢占多次,自己能不能反复抢。iii:抢不到怎么办,自旋?阻塞?或者超时放弃?iv:锁被释放了还在等待锁的线程怎么办?是通知所有线程一起抢或者只告诉一个线程抢
④自旋锁:i:当线程尝试获取锁时,如果锁已经被占用,线程不会立即阻塞,而是通过自旋,循环等待方式不断尝试获取锁。用于锁持有时间短的场景,ReentrantLock的tryLock方法就用到自旋锁。ii:优点:避免线程切换带来的开销,缺点是如果锁被占用时间过长,会导致线程空转,浪费 CPU 资源。iii:默认自旋锁会一直等待,直到获取到锁为止。实际开发设置自旋次数或者超时时间。如果超过阈值,线程放弃锁或者进入阻塞状态。

在这里插入图片描述

25、悲观锁、乐观锁

①悲观锁:认为每次访问共享资源时都会发生冲突,所在在操作前一定要先加锁,防止其他线程修改数据。②乐观锁:认为冲突不会总是发生,在操作前不加锁,而是在更新数据时检查是否有其他线程修改数据。如果发现数据被修改,就会重试。

26、并发工具类:CountDownLatch

①定义:是JUC中同步工具类,协调多个线程之间同步,确保主线程在多个子线程完成任务后继续执行。核心思想通过一个倒计时计数器来控制多个线程的执行顺序。②过程:i:初始化一个 CountDownLatch 对象,指定一个计数器的初始值表示需要等待的线程数量。ii:然后在每个子线程执行完任务后,调用countDown()方法,计数器减1。iii:主线程调用await()方法进入阻塞状态,直到计数器为0,所有子线程都执行完任务后,主线程才会继续执行。Q:要查10万多条数据,用线程池分成20个线程去执行,怎么做到等所有的线程都查找完之后,最后一条结果查找结束才输出结果?
A:使用CountDownLatch来实现。CountDownLatch非常适合这个场景。i:创建 CountDownLatch 对象,初始值设定为20表示20个线程需要完成任务。ii:创建线程池,每个线程执行查询操作,查询完毕后调用countDown()方法,计数器减 1。iii:主线程调用await()方法,等待所有线程执行完毕。

27、并发工具类:CyclicBarrier

①定义:可循环使用的屏障,用于多个线程相互等待,直到所有线程都到达屏障后再同时执行。
②过程:i:初始化一个CyclicBarrier对象,指定一个屏障值N,表示需要等待的线程数量。ii:每个线程执行await()方法,表示已经到达屏障,等待其他线程,此时屏障值会减1。iii:当所有线程都到达屏障后,屏障值为0时,所有线程会继续执行。③区别:
CyclicBarrier让所有线程相互等待,全部到达后再继续;CountDownLatch让主线程等待所有子线程执行完再继续。

在这里插入图片描述

27、并发工具类:Semaphore

①定义:信号量控制同时访问某个资源的线程数量,确保最多只有指定数量线程能够访问某个资源,超过的必须等待。
②过程:i:初始化一个Semaphore对象,指定许可证数量,表示最多允许多少个线程同时访问资源。ii:在每个线程访问资源前,调用acquire()方法获取许可证,如果没有可用许可证,则阻塞等待。iii:访问完资源后,要调用release()方法释放许可证。③用途:流量控制,如数据库连接池、网络连接池。

在这里插入图片描述

28、并发工具类:Exchanger

①定义:交换者,在两个线程之间进行数据交换,支持双向数据交换。
②过程:A调用exchange(dataA),线程B调用exchange(dataB),它们会在同步点交换数据,即A得到B的数据,B得到A的数据。如果一个线程先调用exchange()会阻塞等待,直到另一个线程也调用exchange()。使用Exchanger先创建一个Exchanger对象,然后在两个线程中调用exchange()方法进行数据交换。

在这里插入图片描述

29、ConcurrentHashMap

①JDK7特点: HashMap的线程安全版本。i:JDK7是分段锁,整个Map会被分为若干段,每个段都可以独立加锁。不同的线程同时操作不同的段,从而实现并发。ii:HashEntry是一个单项链表,段继承ReentrantLock,每个段都是一个可重入锁。②put过程:i:计算key的hash,定位到段,段如果是空就先初始化;ii:使用ReentrantLock进行加锁,如果加锁失败就自旋,自旋超过次数就阻塞,保证一定能获取到锁;iii:遍历段中的键值对HashEntry,key相同直接替换,key不存在就插入。iv:释放锁。③get过程:i:先计算key的hash找到段,再遍历段中的键值对,找到就直接返回value。ii:get不用加锁,因为是value是volatile修饰,线程读取value时保证可见性。

在这里插入图片描述

JDK8使用一种更加细粒度的桶锁,配合CAS+synchronized代码块控制并发写入,最大程度减少锁的竞争。i:读操作,ConcurrentHashMap使用volatile变量来保证内存可见性。ii:写操作,ConcurrentHashMap优先使用CAS尝试插入,如果成功就返回;否则使用synchronized对代码块加锁处理。①put过程:i:计算key的hash确定桶在数组中的位置。如果数组为空,采用CAS初始化,确保只有一个线程在初始化数组。ii:如果桶为空,直接CAS插入节点。如果CAS操作失败,会退化为synchronized代码块来插入节点。插入过程会判断桶哈希是否小于0,小于0是红黑树,大于等于0是链表。实际红黑树节点TreeBin的hash值固定为-2。iii:如果链表长度超过 8,转换为红黑树。iv:插入新节点后,会调用addCount()方法检查是否需要扩容。②get过程:i:通过key的hash进行定位,如果该位置节点的哈希匹配且键相等,则直接返回值。ii:如果节点的哈希为负数,说明是个特殊节点,比如说如树节点或者正在迁移的节点,调用find方法查找。

在这里插入图片描述

30、CopyOnWriteArrayList

①定义:ArrayList的线程安全版本,适用于读多写少的场景。
核心思想:写操作时创建一个新数组,修改后再替换原数组,确保读操作无锁,提高并发性能。②过程:i:内部使用volatile变量来修饰数组array,保证读操作的内存可见性。ii:写操作的时候使用ReentrantLock来保证线程安全。iii:缺点:写操作的时候会复制一个新数组,如果数组很大,写操作的性能会受到影响。

在这里插入图片描述

30、BlockingQueue

①定义:JUC包下的一个线程安全队列,支持阻塞式的“生产者-消费者”模型。
当队列容器已满,生产者线程被阻塞,直到消费者线程取走元素后为止;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。②实现:i:阻塞队列使用ReentrantLock + Condition来确保并发安全。ii:以ArrayBlockingQueue为例,内部维护一个数组,使用两个指针分别指向队头和队尾。put先用ReentrantLock加锁,然后判断队列是否已满,如果已满就阻塞等待,否则插入元素。

在这里插入图片描述

31、线程池

①定义:管理和复用线程的工具,减少线程的创建和销毁开销。
ThreadPoolExecutor是线程池的核心实现,通过核心线程数、最大线程数、任务队列和拒绝策略来控制线程的创建和执行。使用线程池关注重点:i:合适的线程池大小。过小线程池会导致任务一直在排队;过大线程池会导致大家都在竞争 CPU 资源,增加上下文切换开销。ii:合适的任务队列。有界队列避免资源耗尽风险,但会导致任务被拒绝;无界队列虽然避免任务被拒绝,但会导致内存耗尽。使用LinkedBlockingQueue,传入参数来限制队列中任务的数量,不会出现OOM。iii:使用自定义线程池,而不是使用Executors创建的线程池。因为newFixedThreadPool线程池由于使用LinkedBlockingQueue,队列容量默认无限大,任务过多时会导致内存溢出;newCachedThreadPool线程池由于核心线程数无限大,当任务过多会导致创建大量的线程,导致服务器负载过高宕机。
线程池调优:i:根据任务类型设置核心线程数参数,如IO密集型任务会设置为 CPU 核心数*2 的经验值。ii:结合线程池动态调整的能力,在流量波动时通过setCorePoolSize平滑扩容,或者使用DynamicTp实现线程池参数的自动化调整。iii:通过内置的监控指标建立容量预警机制。通过JMX 监控线程池的运行状态,设置阈值,当线程池的任务队列长度超过阈值时,触发告警。线程池参数动态修改:i:线程池提供的setter方法在运行时动态修改参数。setCorePoolSize修改核心线程数、setMaximumPoolSize修改最大线程数。ii:调用setCorePoolSize()时如果新的核心线程数比原来大,线程池会创建新线程;如果更小,线程池不会立即销毁多余的线程,除非有空闲线程超过keepAliveTime。
②线程池工作流程:任务提交 → 核心线程执行 → 任务队列缓存 → 非核心线程执行 → 拒绝策略处理。i:线程池通过submit()或execute()提交任务。a:execute方法没有返回值,适用于不关心结果和异常的简单任务。b:submit 有返回值,适用于需要获取结果或处理异常的场景.ii:线程池会先创建核心线程来执行任务。iii:如果核心线程都在忙,任务会被放入任务队列中。iv:如果任务队列已满,且当前线程数量小于最大线程数,线程池会创建新的线程来处理任务。v:如果线程池中的线程数量已经达到最大线程数,且任务队列已满,线程池会执行拒绝策略。

在这里插入图片描述

③线程池7参数,重点关注:核心线程数、最大线程数、等待队列、拒绝策略。i:corePoolSize:核心线程数,长期存活,执行任务的主力。ii:maximumPoolSize:线程池允许的最大线程数。iii:workQueue:任务队列,存储等待执行的任务。iv:handler:拒绝策略,任务超载时的处理方式。线程数达到maximumPoolSiz,任务队列也满的时候,触发拒绝策略。a:AbortPolicy:默认拒绝策略,会抛RejectedExecutionException异常。b:CallerRunsPolicy:让提交任务的线程自己来执行这个任务,调用execute方法的线程。c:DiscardOldestPolicy:等待队列会丢弃队列中最老的一个任务,然后尝试重新提交被拒绝的任务。d:DiscardPolicy:丢弃被拒绝的任务,不做任何处理也不抛出异常。v:threadFactory:线程工厂,用于创建线程,可自定义线程名。vi:keepAliveTime:非核心线程的存活时间,空闲时间超过该值就销毁。vii:unit:keepAliveTime参数的时间单位。

在这里插入图片描述

④关闭线程池
i:调用线程池的shutdown或shutdownNow方法来关闭线程池。shutdown不会立即停止线程池,而是等待所有任务执行完毕后再关闭线程池。shutdownNow通过一系列动作来停止线程池,包括停止接收外部提交任务、 忽略队列里等待任务、尝试将正在跑的任务interrupt中断。shutdownNow不会真正终止运行的任务,给任务线程发送interrupt信号,任务是否真正终止取决于线程是否响应InterruptedException。
⑤线程池的线程数配置
分析线程池中执行的任务类型是CPU密集型还是IO密集型i:对于CPU密集型任务,尽量减少线程上下文切换,优化CPU使用率。核心线程数设置为处理器的核心数或核心数加一是较理想选择。+1是以备不时之需,如果某线程因等待系统资源而阻塞时,有多余的线程顶上去,不至于影响整体性能。ii:对于IO密集型任务,由于线程经常处于等待状态,等待IO操作完成,设置更多线程来提高并发,如CPU核心数的两倍。
⑥设置的线程数多了还是少了,如何查看?
通过监控和调试来判断线程数是多还是少。i:通过top命令观察CPU的使用率,如果CPU使用率较低,线程数过少;如果CPU使用率接近100%,但吞吐量未提升,线程数过多。ii:通过VisualVM分析线程运行情况,查看线程的状态、等待时间、运行时间等信息。iii:jstack命令查看线程堆栈信息,查看线程是否处于阻塞状态。有大量的BLOCKED线程说明线程数过多,竞争比较激烈。
⑦四种常见线程池:
本质上都是ThreadPoolExecutor的不同配置。i:固定大小线程池Executors.newFixedThreadPool(int nThreads):用于任务数量确定,且对线程数有明确要求场景。a:线程池大小固定,corePoolSize=maximumPoolSize,默认使用LinkedBlockingQueue阻塞队列,适用于任务量稳定场景。b:新任务提交时,线程池有空闲线程直接执行;如果没有,任务进入LinkedBlockingQueue等待。c:缺点是任务队列默认无界,可能导致任务堆积,甚至 OOM。ii:缓存线程池Executors.newCachedThreadPool():短时间内任务量波动较大场景。短时间内有大量文件处理任务或网络请求。a:线程池大小不固定,corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE。b:空闲线程超过60秒会被销毁,使用SynchronousQueue阻塞队列,适用于短时间内有大量任务的场景。c:提交任务时,线程池没有空闲线程,直接新建线程执行任务;如果有,复用线程执行任务。线程空闲 60 秒后销毁,减少资源占用。d:缺点是线程数没有上限,在高并发情况下可能导致 OOM。iii:定时任务线程池Executors.newScheduledThreadPool(int corePoolSize):定时执行任务场景。a:任务线程池大小可配置,支持周期性任务执行,使用DelayedWorkQueue阻塞队列,适用于周期性执行任务的场景。b:定时任务时,schedule()方法将任务延迟一定时间后执行一次;scheduleAtFixedRate()方法将任务延迟一定时间后以固定频率执行;scheduleWithFixedDelay() 方法将任务延迟一定时间后以固定延迟执行。iv:单线程线程池Executors.newSingleThreadExecutor():按顺序执行任务的场景。a:线程池只有1个线程,保证任务按提交顺序执行,使用LinkedBlockingQueue阻塞队列,适用于需要按顺序执行任务的场景。b:缺点是无法并行处理任务。
⑧五种线程池阻塞队列i:有界队列ArrayBlockingQueue:一个有界的先进先出阻塞队列,底层是一个数组,适合固定大小线程池。ii:无界队列LinkedBlockingQueue:底层是链表,如果不指定大小,默认大小 Integer.MAX_VALUE,相当于一个无界队列。a:任务执行时间比较长,会导致队列任务越积越多,导致内存使用不断飙升,最终出现OOM。iii:优先级队列PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。任务按照其自然顺序或Comparator来排序。适用于需要按照给定优先级处理任务的场景,比如优先处理紧急任务。iv:延迟队列DelayQueue;类似PriorityBlockingQueue,由二叉堆实现的无界优先级阻塞队列。v:同步队列SynchronousQueue:每个插入操作必须等待另一个线程的移除操作,任何一个移除操作都必须等待另一个线程的插入操作。
⑨线程池异常:i:使用try-catch捕获。ii:使用Future获取异常:使用submit(),关心任务返回值。iii:自定义ThreadPoolExecutor重写afterExecute方法:全局捕获所有任务异常。iv:使用UncaughtExceptionHandler捕获异常,使用execute(),不关心任务返回值。

在这里插入图片描述

⑩线程池5种状态
RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED 依次流转。i:RUNNING 状态的线程池可以接收新任务,并处理阻塞队列中的任务.ii:SHUTDOWN 状态的线程池不会接收新任务,但会处理阻塞队列中的任务。iii:STOP 状态的线程池不会接收新任务,也不会处理阻塞队列中的任务,并且会尝试中断正在执行的任务。iv:TIDYING 状态表示所有任务已经终止。v:ERMINATED 状态表示线程池完全关闭,所有线程销毁。

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/82140.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

飞牛fnNAS存储空间模式详解

目录 一、NAS的存储空间 二、多硬盘对NAS速度的提升原理 三、多硬盘对数据安全的提升原理 四、多硬盘对容量的提升原理 五、磁盘阵列模式 六、飞牛NAS支持的存储模式 七、具体如何选择存储空间模式 在数字化时代,数据是个人和企业发展的核心资产,但面临硬盘损坏、病毒…

OpenCv高阶(二十)——dlib脸部轮廓绘制

文章目录 一、人脸面部轮廓绘制代码实现1、定义绘制直线段的函数2、定义绘制凸包轮廓的函数3、读取输入图像4、初始化dlib的人脸检测器5、使用检测器在图像中检测人脸&#xff08;参数0表示不进行图像缩放&#xff09;6、加载dlib的68点人脸关键点预测模型7、遍历检测到的每个人…

WEBSTORM前端 —— 第3章:移动 Web —— 第3节:移动适配

目录 一、移动Web基础 1.谷歌模拟器 2.屏幕分辨率 3.视口 4.二倍图 二、适配方案 三、rem 适配方案 四、less 1.less – 简介 2.less – 注释 3.less – 运算 4.less – 嵌套 5.less – 变量 6.less – 导入 7.less – 导出 8.less – 禁止导出 五…

Altium Disigner(16.1)学习-原理图绘制以及必要操作

一、下载软件 通过网盘分享的文件&#xff1a;Altium Designer 16.zip 链接: https://pan.baidu.com/s/1uBHeoJJ-iA2tXw3NRjCcdA?pwd7c3h 提取码: 7c3h 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v5的分享 二、建立工程 添加proje…

AI炼丹日志-25 - OpenAI 开源的编码助手 Codex 上手指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 大数据篇 300&#xff1a; Hadoop&…

Redis:安装与常用命令

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Redis &#x1f525; 安装 Redis 使⽤apt安装 apt install redis -y⽀持远程连接 修改 /etc/redis/redis.conf 修改 bind 127.0.0.1 为 bind 0.0.0.0 修改 protected-mode yes 为 protected-mo…

02 APP 自动化-Appium 运行原理详解

环境搭建见 01 APP 自动化-环境搭建 文章目录 一、Appium及Appium自动化测试原理二、Appium 自动化配置项三、常见 ADB 命令四、第一个 app 自动化脚本 一、Appium及Appium自动化测试原理 Appium 跨平台、开源的 app 自动化测试框架&#xff0c;用来测试 app 应用程序&#x…

UDP/TCP协议全解

目录 一. UDP协议 1.UDP协议概念 2.UDP数据报格式 3.UDP协议差错控制 二. TCP协议 1.TCP协议概念 2.三次握手与四次挥手 3.TCP报文段格式&#xff08;重点&#xff09; 4.流量控制 5.拥塞控制 一. UDP协议 1.UDP协议概念 当应用层的进程1要向进程2传输报文&#xff…

AWS之数据分析

目录 数据分析产品对比 1. Amazon Athena 3. AWS Lake Formation 4. AWS Glue 5. Amazon OpenSearch Service 6. Amazon Kinesis Data Analytics 7. Amazon Redshift 8.Amazon Redshift Spectrum 搜索服务对比 核心功能与定位对比 适用场景 关键差异总结 注意事项 …

第13讲、Odoo 18 配置文件(odoo.conf)详细解读

1. 概述 Odoo 配置文件&#xff08;odoo.conf&#xff09;是管理 Odoo 服务器行为的核心工具&#xff0c;涵盖了网络、安全、数据库、性能等多方面的关键参数。本文档系统梳理 Odoo 18 配置文件的主要参数&#xff0c;结合实际应用场景&#xff0c;提供权威的配置建议与最佳实…

Python详细实现Dash仪表盘:从零构建数据可视化界面

目录 Python详细实现Dash仪表盘&#xff1a;从零构建数据可视化界面一、引言&#xff1a;为什么选择 Dash&#xff1f;二、Dash 的核心组成与工作流程三、项目目标使用数据&#xff1a; 四、数学模型与聚合公式五、仪表盘结构设计页面设计结构如下&#xff1a; 六、完整代码实现…

ubuntu磁盘挂载

在 Ubuntu 系统中&#xff0c;挂载额外的磁盘或分区是一项常见操作&#xff0c;无论是为了扩展存储空间还是组织数据。本文将详细介绍如何使用mount命令挂载文件系统&#xff0c;并处理可能遇到的问题。 1、挂载文件系统的基本步骤 1.1、 查找磁盘设备名称 首先需要确定要挂载…

[9-2] USART串口外设 江协科技学习笔记(9个知识点)

1 2 3 智能卡、IrDA和LIN是三种不同的通信技术&#xff0c;它们在电子和汽车领域中有着广泛的应用&#xff1a; • 智能卡&#xff08;Smart Card&#xff09;&#xff1a; • 是什么&#xff1a;智能卡是一种带有嵌入式微处理器和存储器的塑料卡片&#xff0c;可以存储和处理数…

【js逆向_AES】全国二手房指数数据爬取

目标&#xff1a;请求参数signcode&#xff0c;请求结果data。 网址&#xff1a;aHR0cDovL3d3dy5jY2hpbmRleC5jb20vSG9tZS9pbmRleA 查看载荷 查看响应数据 点击xhr&#xff0c;发现所有请求参数都是一个signCode&#xff0c;还是加密后的结果&#xff0c;对应结果中数据data也…

模块化设计,static和extern(面试题常见)

文章目录 一、函数的声明和定义1.1 单个文件1.2 多个文件1.3 static和extern1.3.1 static修饰局部变量1.3.2 static修饰全局变量1.3.3 static修饰函数 总结 一、函数的声明和定义 1.1 单个文件 一般我们在使用函数的时候&#xff0c;直接将函数写出来就使用了 题目:写一个函数…

PySide6 GUI 学习笔记——常用类及控件使用方法(地址类QUrl)

文章目录 地址类QUrl主要功能URL 格式介绍常见 scheme&#xff08;协议&#xff09;类型QUrl 类常用方法常用方法示例典型应用场景 地址类QUrl QUrl 是 PySide6.QtCore 模块中的一个类&#xff0c;用于处理和操作 URL&#xff08;统一资源定位符&#xff09;。它可以解析、构建…

GEE:获取研究区的DEM数据

最近有粉丝追更 GEE 系列,说上次看完 DEM 代码解析后,自己试着改了一版。今天咱们就来拆解他的优化版代码 ——基于 SRTM 数据获取研究区 DEM 并导出,顺便聊聊怎么把 GEE 代码写得更专业! 先下结论:代码逻辑完整,3 处细节值得新手抄作业! 这版代码在数据加载→裁剪→可…

汽车安全 2030 预测 (功能安全FuSa、预期功能安全SOTIF、网络安全CyberSecurity):成本、效益与行业影响

汽车安全 2030 预测 (功能安全FuSa、预期功能安全SOTIF、网络安全CyberSecurity)&#xff1a;成本、效益与行业影响 到 2030 年&#xff0c;汽车行业将迎来一场安全技术的深度变革&#xff0c;其中 “三重安全防护”&#xff08;功能安全 FuSa、预期功能安全 SOTIF、网络安全&…

深入理解设计模式之状态模式

深入理解设计模式之&#xff1a;状态模式&#xff08;State Pattern&#xff09; 一、什么是状态模式&#xff1f; 状态模式&#xff08;State Pattern&#xff09;是一种行为型设计模式。它允许一个对象在其内部状态发生改变时&#xff0c;改变其行为&#xff08;即表现出不…

Redis的大Key问题如何解决?

大家好&#xff0c;我是锋哥。今天分享关于【Redis的大Key问题如何解决&#xff1f;】面试题。希望对大家有帮助&#xff1b; Redis的大Key问题如何解决&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis中的“大Key”问题是指某个键的值占用了过多…