binidx - Java
type: channel
link: https://ady.woms.cloud/tag/Java/
items: 记一次函数传参导致的异常
items: Java-基础
items: 算法
items: 分布式
items: Spring
items: 简单的锁实现
items: JVM
items: 锁
items: 多线程

记一次函数传参导致的异常
type: item
date: 2025-04-14T16:03:26+08:00
description: 背景在开发过程中，为了实现逻辑复用，我们重写并抽离了一部分公共方法。这些方法通过 Feign 调用 Oms 系统，用于支持多客户导入功能。在此过程中，需要记录成功列表和错误列表，并最终返回给调用方。问题描述在重构代码时，为了去重，我们将 errorList 的类型从 List 改为 Set，并在方法内部使用了以下代码：new ArrayList<>(errorSet)来对 errorList 进行重新赋值。然而，这种操作导致了一个严重的问题：虽然 errorSet 中的数据被正确处理了，但 errorList 在上层方法中仍然是空的。原因分析引用问题：Java 中的集合（如 List 和 Set）是引用类型。使用 new ArrayList<>(errorSet) 会创建一个新的对象，生成新的内存地址和引用。上层方法持有的仍然是原来的 errorList 引用，而方法内部的操作并未修改该引用，因此上层方法无法感知到错误信息的变化。IDEA 警告：IDEA 在代码中给出了警告，但每当回事，未及时处理。警告内容可能类似于："Reassignment of method parameter 'errorList'" 或 "Possible unintended reference modification"。设计缺陷：方法签名中直接传递了 List<OmpProductExcel> errorList，并通过方法内部对其重新赋值。这种设计容易引发误解，因为调用者通常期望传入的参数不会被重新赋值。解决方案为了避免上述问题，可以采取以下改进措施：方法一：直接操作传入的集合[最终解决方案]不重新赋值 errorList，而是直接对其进行操作：private List<OmpProductExcel> batchValidCommon( AbstractExcelListener.SimpleCheckParam<OmpProductExcel> param, List<OmpProductExcel> targetList, List<OmpProductExcel> errorList, boolean customerFlag) { Set<OmpProductExcel> errorSet = new HashSet<>(); // 填充 errorSet 的逻辑 errorSet.addAll(...); // 直接将 errorSet 的内容添加到 errorList errorList.addAll(errorSet); // 返回成功列表或其他逻辑 return targetList; }这种方式保持了 errorList 的引用不变，确保上层方法能够感知到数据的变化。方法二：返回新的集合如果确实需要返回一个新的集合，可以通过方法的返回值传递，而不是直接修改传入的参数：private List<OmpProductExcel> batchValidCommon( AbstractExcelListener.SimpleCheckParam<OmpProductExcel> param, List<OmpProductExcel> targetList, boolean customerFlag) { Set<OmpProductExcel> errorSet = new HashSet<>(); // 填充 errorSet 的逻辑 errorSet.addAll(...); // 返回新的错误列表 return new ArrayList<>(errorSet); }调用方可以根据返回值获取错误列表，避免了引用问题。方法三：使用包装类如果需要同时返回多个结果，可以使用一个包装类：public class ValidationResult<T> { private List<T> successList; private List<T> errorList; // Getters and Setters } private ValidationResult<OmpProductExcel> batchValidCommon( AbstractExcelListener.SimpleCheckParam<OmpProductExcel> param, List<OmpProductExcel> targetList, boolean customerFlag) { Set<OmpProductExcel> errorSet = new HashSet<>(); // 填充 errorSet 的逻辑 errorSet.addAll(...); ValidationResult<OmpProductExcel> result = …

Java-基础
type: item
date: 2025-02-24T08:14:15+08:00
description: Java集合：普通集合：List（列表）底层实现：ArrayList 和 LinkedList。ArrayList 基于动态数组，支持快速随机访问元素，但插入和删除操作（特别是中间位置的操作）较慢。LinkedList 基于双向链表，允许在任何位置进行快速的插入和删除操作，但随机访问速度较慢。适用场景：当你需要一个可以容纳重复元素的有序集合，并且可能需要频繁地遍历或访问元素时使用。Set（集）底层实现：HashSet, LinkedHashSet 和 TreeSet。HashSet 使用哈希表实现，不保证元素的顺序，不允许重复元素，提供常数时间的基本操作（添加、移除、查询）。LinkedHashSet 维护了一个双向链表以保持元素的插入顺序，同时利用哈希表保证了性能。TreeSet 使用红黑树实现，能保持元素按自然顺序或者自定义顺序排序，不允许重复元素，但操作的时间复杂度为O(log n)。适用场景：当你需要确保集合中没有重复元素，并且/或者希望按照特定顺序处理这些元素时使用。Queue（队列）底层实现：LinkedList, PriorityQueue 等。LinkedList 可以作为双端队列使用，支持在两端进行插入和移除操作。PriorityQueue 使用堆实现，队列头部是最小（或最大，取决于构造函数）的元素。适用场景：当需要遵循“先进先出”（FIFO）原则或优先级队列（基于优先级处理元素）时使用。Deque（双端队列）底层实现：ArrayDeque 和 LinkedList。ArrayDeque 是基于循环数组实现的，允许在两端高效地添加和移除元素。LinkedList 同样可以用作双端队列。适用场景：当你需要在一个序列的两端进行高效地插入和删除操作时使用。Map（映射）底层实现：HashMap, LinkedHashMap 和 TreeMap。HashMap 使用哈希表实现，键值对之间无序，查找、插入和删除操作平均时间为常数时间。LinkedHashMap 在HashMap的基础上增加了双向链表来维护元素的插入顺序或访问顺序。TreeMap 基于红黑树实现，键值对会按键的自然顺序或者自定义比较器排序，操作的时间复杂度为O(log n)。适用场景：当你需要通过键来快速查找、插入、删除对应的值时使用。线程安全集合：ConcurrentHashMap设计与实现：基于哈希表实现，但使用了分段锁（Segment）技术来减少锁的竞争。每个Segment都继承自ReentrantLock，可以独立加锁，从而允许多个线程同时访问不同的段。线程安全策略：通过将数据分成多个段，使得不同段的数据可以被并发地修改和读取，极大地提高了并发性能。CopyOnWriteArrayList 和 CopyOnWriteArraySet设计与实现：写操作时进行复制整个底层数组，在副本上进行修改，完成后将引用指向新的数组。适用于读多写少的场景。线程安全策略：写操作是通过创建底层数组的新副本来实现的，这避免了传统同步列表在写操作时需要锁定整个列表的问题，因此读操作可以在不锁定的情况下进行。ConcurrentLinkedQueue 和 ConcurrentLinkedDeque设计与实现：基于无锁算法实现的先进先出（FIFO）队列，使用CAS（Compare-And-Swap）操作保证线程安全。线程安全策略：利用原子变量和非阻塞同步机制来确保线程安全性，特别适合于高并发环境下的队列操作。BlockingQueue 接口及其实现类（如 LinkedBlockingQueue, ArrayBlockingQueue）设计与实现：提供了一个线程安全的队列接口，其内部使用锁来保护状态，某些实现还提供了有界队列的支持。线程安全策略：通过显式的锁和条件对象来控制对队列的访问，允许生产者等待队列不满以及消费者等待队列不空的状态。SynchronousQueue设计与实现：不存储元素的队列，每一个put操作必须等待一个take操作，反之亦然。线程安全策略：直接在线程间传递数据，没有额外的缓冲区，确保了线程间的直接通信。Collections.synchronizedXXX 方法包装的标准集合例如 synchronizedList, synchronizedMap 等，通过对标准集合的方法加上 synchronized 关键字来达到线程安全的目的。线程安全策略：虽然简单易用，但由于所有的方法调用都需要获取同一个锁，所以在高并发情况下性能可能不如专门设计的并发集合。List使用注意事项：IO模型：BIO (Blocking I/O)描述：BIO 是传统的阻塞式I/O模型，所有的socket连接都是基于一个线程对应一个请求的方式进行处理。当一个线程执行读或写操作时，如果数据未…

算法
type: item
date: 2025-02-23T10:40:00+08:00
description: [info] todo [/info]

分布式
type: item
date: 2025-02-23T10:40:00+08:00
description: CAP：CAP理论是分布式计算系统中的一个基本理论，由Eric Brewer提出，并由Seth Gilbert和Nancy Lynch正式证明。它指出在一个分布式系统中，不可能同时满足以下三个属性：一致性（Consistency）：每个读操作都能读到最新的写结果，即所有节点在同一时刻看到的数据是一致的。在分布式系统中，这意味着任何数据更新必须在所有相关的副本上生效后，才能被认为是成功的。可用性（Availability）：每个请求都能得到一个响应，不管这个响应成功与否。即使部分节点故障，系统整体仍然可以处理客户端的请求。分区容错性（Partition Tolerance）：尽管网络可能失败（如分隔成多个独立的子网），系统仍然能够运作。这意味着即使某些消息丢失或延迟，系统依然能够提供服务。考虑一个简单的例子，假设我们有两个节点A和B，它们之间需要保持一致性和应对网络分区。如果我们要求系统具有分区容错性（P），那么在网络分区发生时，A和B之间的通信可能会中断。如果在这个时候我们也要求系统具有一致性（C），那么当A接收到了一个写请求并更新了它的状态，由于无法与B通信，B将不会知道这次更新。因此，如果一个读请求到达B，它将返回过期的数据，这违反了一致性的原则。为了保持一致性，我们必须阻止B响应读请求直到它从A那里得到了更新的信息，这就牺牲了可用性（A）。另一方面，如果我们选择可用性（A），即使在网络分区的情况下，A和B都将继续接受读写请求。然而，在这种情况下，如果A和B不能同步它们的状态，系统就不能保证一致性（C）。因此，选择了可用性就意味着放弃了强一致性。所以，在设计分布式系统时，通常需要在这三者之间做出权衡，根据具体的应用场景来决定放弃哪一个属性。例如：选择CP（一致性+分区容错性）意味着在网络出现问题时，系统会选择等待直到确保所有节点都达到一致状态，这可能会导致某些节点暂时不可用。选择AP（可用性+分区容错性）则意味着即使在网络问题期间，系统也会继续响应请求，但可能会返回不完全一致的结果。选择CA（一致性+可用性）实际上是不可能实现的，因为在实际的分布式环境中，网络分区是一个不可避免的事实。如果要避免分区，那就意味着系统不能真正分布，也就不存在所谓的分布式系统了。网络分区问题：在网络分区（Network Partition）的情况下，分布式系统中的不同部分可能无法相互通信，导致原本作为一个整体运行的系统分裂成多个独立的部分。这种情况在Redis或Zookeeper这样的集群环境中被称为“脑裂”（Split Brain）。当脑裂发生时，每个独立的部分可能会继续处理读写请求，而不知道其他部分的存在或状态，这可能导致数据不一致、重复的数据更新等问题。脑裂现象以Zookeeper为例，它是一个为分布式应用提供协调服务的工具。如果一个由3个节点组成的Zookeeper集群中出现了网络分区，使得两个节点可以互相通信但与第三个节点失去联系，这时就可能发生脑裂。如果这两个能互相通信的节点认为它们构成了多数派，并继续进行选举和操作，那么剩下的那个孤立节点也可能尝试形成自己的多数派或者单独行动，这就形成了两个独立的工作组，各自认为自己是合法的领导者，这就是所谓的脑裂现象。对于Redis集群来说，脑裂可能导致主从复制出现问题，比如两个不同的节点都认为自己是当前的主节点，接受写操作，而这会导致数据不一致的问题。解决方法Quorum机制：这是最常用的解决脑裂问题的方法之一。通过设置一个大多数原则（如Paxos或Raft算法），确保只有拥有超过半数成员支持的分区才能进行写操作。例如，在一个5节点的集群中，至少需要3个节点的同意才能执行写入操作。这种方法有效地避免了脑裂问题，因为它保证了任何时候只有一个分区能够合法地处理写请求。TTL（Time To Live）或租约机制：在这种机制下，领导者会定期发送心跳信号给所有跟随者，并且每次都会附带一个新的TTL值。如果跟随者在TTL时间内没有收到心跳信号，则认为当前领导者失效，并触发新的领导者选举过程。这种方法可以帮助减少由于临时网络问题导致的脑裂风险。版本号或时间戳：为了防止不同分区上的数据冲突，可以使用版本号或时间戳来标记数据。当网络恢复后，可以通过比较版本号或时间戳来决定哪个副本是最新的，并以此为基础进行数据同步。手动干预：在一些情况下，可能需要管理员手动介入来解决脑裂问题。比如确定哪个分区应该被认为是权威的，并基于此来修复数据一致性问题。分布式事务：2PC：2PC：两阶段提交，将事务的提交过程分为资源准备和资源提交两个阶段，并且由事务协调者来协调所有事务参与者，如果准备阶段所有事务参与者都预留资源成功，则进行第二阶段的资源提交，否则事务协调者回滚资源。协调者向所有参与者发送事务内容，询问是否可以提交事务，并…

Spring
type: item
date: 2025-02-23T10:39:00+08:00
description: 注解：核心注解@Component：通用组件注解，用于标记一个类为Spring管理的组件（Bean）。它可以被@Service, @Repository, 和 @Controller等更特定的注解所替代。使用@Component注解时，在解析@Component注册为BeanDefinition时会调用AnnotationBeanNameGenerator.buildDefaultBeanName，此时正常情况下会将第一个字符从大写转为小写，但如果第一个和第二个都是大写，则不变。【FooBah->fooBah; URL->URL】@Service：标记服务层组件，通常用于业务逻辑层。它是一个特化的@Component，有助于分层架构的理解和维护。@Repository：标记数据访问层（DAO层）组件，用于持久层操作。同样也是@Component的一种具体形式，主要用于数据库交互。@Controller：用于标记Web控制器组件，通常与Spring MVC一起使用处理HTTP请求。@RestController：是@Controller和@ResponseBody的组合注解，专门用于创建RESTful Web服务。依赖注入相关注解@Autowired：自动装配依赖项，可以用于构造函数、字段或setter方法上。默认情况下，它会根据类型进行匹配。@Qualifier：与@Autowired结合使用，当有多个相同类型的Bean时，用来指定具体的Bean。@Value：用于注入外部配置文件中的属性值，或者直接指定字符串值。@Resource：类似于@Autowired，但它是基于名字而非类型的匹配，默认情况下。@Inject：JSR-330标准中定义的注解，功能类似@Autowired，但不支持一些高级特性如required=false。配置相关注解@Configuration：标识一个类为配置类，包含Bean定义的方法。@Bean：在@Configuration类中使用，标明一个方法返回的对象将被注册为Spring容器中的Bean。@ComponentScan：指示Spring扫描指定包下的组件（包括@Component, @Service, @Repository, @Controller等），以便将其注册为Spring Bean。@PropertySource：加载properties文件到Spring环境中。@Import：允许从另一个配置类导入Bean定义。@EnableAutoConfiguration：启用自动配置，内部实际上就去加载META-INF/spring.factories文件的信息，然后筛选出以EnableAutoConfiguration为key的数据，加载到IOC容器中，实现自动配置功能！AOP相关注解@Aspect：定义一个切面类。@Before, @After, @Around 等：这些注解用于定义切点处的增强逻辑。其他注解@Transactional：声明事务边界，通常用在服务层。@RequestMapping：用于映射web请求到处理器方法，包括GET, POST等请求方式。其变体有@GetMapping, @PostMapping等。@Transactional：@Transactional(readOnly = true)实际上就是利用数据库的SET TRANSACTION READ ONLY， 开启只读事务，这意味着在此事务内部，任何修改数据的操作（如 INSERT、UPDATE、DELETE）都将被禁止，只能执行读取操作（如 SELECT）；只读事务依然会运用上隔离级别（MVCC），需要事务隔离级别需要一定性能开销。当执行查询操作时，如果不需要事务的隔离级别和一致性保证，并且不需要使用事务管理的功能，那么不开启事务可能是更为合适的选择。这种情况下，查询操作将立即执行并返回结果，不会受到事务管理和隔离级别的开销影响。然而，需要注意的是，如果应用需要保证数据的一致性和隔离性，并且希望查询操作与其他事务的修改行为相互独立，那么开启只读事务是必要的。在这种情况下，虽然可能会有一些额外的性能开销，但可以保证数据的一致性和隔离性，避免了并发操作可能引起的数据不一致问题。Spring事务：Spring事务底层是基于数据库事务和AOP机制的。首先对于使用了@Transactional注解的Bean，Spring会创建一个代理对象作为Bean。当调用代理对象的方法时，会先判断该方法上是否加了@Transactional注解。如果加了，那么则利用事务管理器创建一个数据库连接。并且修改数据库连接的autocommit属性为false，禁止此连接的自动提交。然后执行当前方法，方法中会执行sql。执行完当前方法后，…

简单的锁实现
type: item
date: 2025-02-22T23:30:00+08:00
description: import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; public class MyLock { AtomicInteger state = new AtomicInteger(0); Thread owner = null; AtomicReference<Node> head = new AtomicReference<>(new Node()); AtomicReference<Node> tail = new AtomicReference<>(head.get()); void lock() { //这里支持可重入，现在是非公平的 //注释掉这里的，可以变为公平锁 //todo:可以拆一个tryLock方法 if (state.get() == 0) { if (state.compareAndSet(0, 1)) { System.out.println(Thread.currentThread().getName() + "直接拿到锁"); owner = Thread.currentThread(); return; } } else { if (owner == Thread.currentThread()) { System.out.println(Thread.currentThread().getName() + " 拿到了重入锁,当前重入次数为" + state.incrementAndGet()); return; } } Node current = new Node(); current.thread = Thread.currentThread(); while (true) { Node currentTail = tail.get(); if (tail.compareAndSet(currentTail, current)) { System.out.println(Thread.currentThread().getName() + "加入到了链表尾"); current.pre = currentTail; currentTail.next = current; break; } } while (true) { if (current.pre == head.get() && state.compareAndSet(0, 1)) { owner = Thread.currentThread(); head.set(current); current.pre.next = null; current.pre = null; System.out.println(Thread.currentThread().getName() + "被唤醒之后，拿到锁"); return; } LockSupport.park(); } } void unlock() { if (Thread.currentThread() != this.owner) { throw new IllegalStateException("当前线程并没有锁，不能解锁"); } int i = state.get(); if (i > 1) { state.set(i - 1); System.out.println(Thread.currentThread().getName() + "解锁了重入锁，重入锁剩余次数" + (i - 1)); return; } if (i <= 0) { throw new IllegalStateException("重入锁解锁错误！"); } Node headNode = head.get(); Node next = headNode.next; state.set(0); if (next != null) { System.out.println(Thread.currentThread().getName() + "唤醒了" + next.thread.getName()); LockSupport.unpark(next.thread); } } class Node { Node pre; Node next; Thread thread; } }

JVM
type: item
date: 2025-02-22T21:12:00+08:00
description: JVM基础：结构：程序计数器（Program Counter Register）：每个线程都有自己的程序计数器，是一个小内存空间。它记录了当前线程执行的字节码的地址，帮助CPU知道下一步要执行的指令是哪个。如果线程正在执行Java方法, 那么记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是Native方法,计数器值为空。 这也是唯一一个在JVM规范中不报内存溢出的区域。Java虚拟机栈（Java Virtual Machine Stack）：虚拟机栈是线程私有的，他的生命周期和线程的生命周期是一致的。里面装的是一个一个的栈帧，每一个方法在执行的时候都会创建一个栈帧，栈帧中用来存放（局部变量表、操作数栈 、动态链接 、返回地址）；在Java虚拟机规范中，对此区域规定了两种异常状况：如果线程请求的栈深度大于虚拟机所允许的深度，将会抛出StackOverflowError异常；如果虚拟机栈动态扩展时无法申请到足够的内存，就会抛出OutOfMemoryError异常。局部变量表：局部变量表是一组变量值存储空间，用来存放方法参数、方法内部定义的局部变量, 局部变量表存放了编译期可以知道的基本数据类型, 对象引用和returnAddress类型, 底层是变量槽（variable slot）。操作数栈：是用来记录一个方法在执行的过程中，字节码指令向操作数栈中进行入栈和出栈的过程。大小在编译的时候已经确定了，当一个方法刚开始执行的时候，操作数栈中是空发的，在方法执行的过程中会有各种字节码指令往操作数栈中入栈和出栈。动态链接：因为字节码文件中有很多符号的引用，这些符号引用一部分会在类加载的解析阶段或第一次使用的时候转化成直接引用，这种称为静态解析；另一部分会在运行期间转化为直接引用，称为动态链接。返回地址（returnAddress）：类型（指向了一条字节码指令的地址）JIT即时编译器（Just In Time Compiler），简称 JIT 编译器: 为了提高热点代码的执行效率，在运行时，虚拟机将会把这些代码编译成与本地平台相关的机器码，并进行各种层次的优化，比如锁粗化等。注意, HotSpot虚拟机只要申请栈空间成功就不会发生OOM(不允许动态拓展栈容量)本地方法栈（Native Method Stack）：与虚拟机栈类似，但用于处理本机方法。Java程序使用JNI（Java Native Interface）调用其他语言（如C/C++）编写的程序，此时本地方法栈会被使用。在HotSpot虚拟机实现中是把本地方法栈和虚拟机栈合二为一的，同理它也会抛出StackOverflowError和OOM异常。堆（Heap）：JVM用来存储对象实例和数组，它是所有线程共享的区域，也是垃圾回收的主要区域。开启逃逸分析后，某些未逃逸对象可以通过标量替换在栈上分配。堆细分：新生代、老年代，对于新生代又分为：Eden区和Surviver1和Surviver2区。元空间（MetaSpace）：存储已经被java虚拟机加载的类信息、常量、静态变量。元空间不是堆的一部分，而是存放在本机内存中，不受JVM堆内存限制，更好利用内存。减少对应用程序行为依赖，更好管理类及元数据的内存分配，使类加载器更可靠的执行类的动态卸载。利用OS的内存，使得JVM启动阶段和内存管理更为高效，减少了永久代相关的Full GC。JVM参数：堆内存设置-Xms<size>：设置JVM启动时的初始堆大小（例如，-Xms512m表示初始堆大小为512MB）。-Xmx<size>：设置JVM允许的最大堆大小（例如，-Xmx2g表示最大堆大小为2GB）。-Xmn<size>：设置年轻代的大小（仅适用于某些垃圾收集器，如Serial, Parallel）。垃圾回收（GC）相关-XX:+UseG1GC：启用G1垃圾收集器。-XX:+UseConcMarkSweepGC：启用CMS（Concurrent Mark Sweep）垃圾收集器。-XX:+UseParallelGC：启用并行垃圾收集器（年轻代使用并行收集，老年代使用单线程标记-清除）。-XX:MaxGCPauseMillis=<value>：设置目标最大GC暂停时间（仅对G1有效）。-XX:+PrintGCDetails：打印详细的GC日志信息。性能调优-XX:PermSize=<size> 和 -XX:MaxPermSize=<size>：设置永久代（方法区）的初始大小和最大大小（在Java 7及之前版本中使用；Java 8之后被Metaspace取代）。-XX:MetaspaceSize=<size> 和 -XX:MaxMetaspaceSize=<size>：设置元空间的初始大小和最大大小（Java 8及以上版本）。-XX:+Agg…

锁
type: item
date: 2025-02-22T21:11:00+08:00
description: volatile底层实现：volatile可以保证数据的可见性，被volatile修饰的变量能够保证每个线程能够获取该变量的最新值，从而避免出现数据脏读的现象；同时禁止指令重排。加入volatile关键字时，汇编代码中会多出一个lock前缀指令，这个指令实际上相当于一个内存屏障（也成内存栅栏），内存屏障会提供3个功能：它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置，也不会把前面的指令排到内存屏障的后面；它会强制将对缓存的修改操作立即写入主存；如果是写操作，它会导致其他CPU中对应的缓存行无效。AQS：AQS，全称AbstractQueuedSynchronizer（抽象队列同步器），是Java并发包（java.util.concurrent）中用于构建锁和其他同步组件的基础框架。它提供了一个框架，用于实现依赖于先进先出（FIFO）等待队列的阻塞锁和相关同步器（信号量、事件等）。AQS支持独占和共享两种模式设计原理：状态管理：AQS使用一个int类型的状态变量表示当前同步状态。这个状态可以通过getState()、setState(int newState)和compareAndSetState(int expect, int update)方法进行查询和更新。FIFO队列：当一个线程尝试获取锁但失败时（即状态不符合获取条件），该线程会被封装成一个节点（Node），并被加入到同步队列的尾部。队列遵循FIFO原则，即先入队的线程优先获得锁。独占与共享模式：AQS支持两种模式来适应不同的同步需求。在独占模式下，每次只有一个线程能够成功获取锁；而在共享模式下，多个线程可能同时成功获取锁。自旋与阻塞：为了提高性能，AQS在某些情况下会让线程自旋（忙等）一段时间而不是立即进入阻塞状态，这取决于具体实现和系统配置。应用：ReentrantLock：一个可重入的互斥锁，提供了与使用synchronized关键字类似的同步语义，并扩展了诸如公平性选择、非块结构锁定操作等功能。Semaphore（信号量）：控制对某组资源的访问权限，限制同时访问某一资源的最大线程数。CountDownLatch：允许一个或多个线程一直等待，直到其他线程执行的一组操作完成为止。CyclicBarrier：允许一组线程互相等待，直到到达某个公共屏障点（所有线程都到达此点后才能继续执行）。ReadWriteLock：允许多个读操作并发执行，但在写操作发生时不允许任何读操作。【多用于缓存系统、配置管理、文档编辑等】锁优化：使用层面：减少锁的时间：不需要同步执行的代码，尽量放到同步块外边（循环的话，可能需要锁粗化，防止频繁加解锁）。减少锁的粒度：物理上的一个锁，拆分为逻辑上的多个锁，增加并行度，以空间换时间，例如java中的ConcurrentHashMap（使用多个segment分别进行加解锁）。使用读写锁：使用ReentranReadWriteLock，读加读锁，写加写锁。使用CAS：线程竞争不激烈，且执行快，可以考虑valatiled+cas。系统层面：自适应自旋锁：自动自旋，自旋时间可以有上一个在同一个锁上的自旋时间和锁的持有者状态决定。锁消除：如果编译器发现不可能存在共享数据的竞争，会进行锁消除【Netty中无锁设计的pipeline中的chnnelhandler会进行锁消除的优化】锁升级：偏向锁->轻量级锁->重量级锁ReentrantLock和synchronized：synchronized：synchronized基于监视器锁（Monitor Lock）实现，底层依赖于操作系统的互斥锁（Mutex）。每个对象在Java中都与一个隐式的监视器相关联，使用synchronized时，线程需要获得对象的监视器锁，然后才能进入同步代码块。synchronized申请锁、上锁、释放锁都与对象头有关。对象头主要结构是由Mark Word 组成，其中Mark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息。同步代码块是利用 monitorenter 和 monitorexit 指令实现的，而同步方法则是利用 flags 实现的。使用：修饰实例⽅法: 作⽤于当前对象实例加锁，进⼊同步代码前要获得当前对象实例的锁修饰静态⽅法: 也就是给当前类加锁，会作⽤于类的所有对象实例，因为静态成员不属于任何⼀个实例对象，是类成员。修饰代码块: 指定加锁对象，对给定对象加锁，进⼊同步代码库前要获得给定对象的锁。偏向锁、轻量级锁、重量级锁：偏向锁：在锁对象的对象头中记录一下当前获取到该锁的线程ID，该线程下次如果又来获取该锁就可以直接获取到了。一旦有另一个线程尝试获取这个锁，偏向锁将被撤销，并可能升级为轻量级锁或者直接升级为重量级锁，取决于竞争的程度。轻量…

多线程
type: item
date: 2025-02-22T21:10:00+08:00
description: 线程状态：wait和sleep：wait()属于Object方法，调用之后会强制释放当前对象锁，调用时必须拿到当前对象的监视器monitor对象，所以必须在 synchronized 保护的代码中使用，而 sleep 方法并没有这个要求。wait 方法会主动释放 monitor 锁，在同步代码中执行 sleep 方法时，并不会释放 monitor 锁。wait 方法意味着永久等待，直到被中断或被唤醒才能恢复，不会主动恢复，sleep 方法中会定义一个时间，时间到期后会主动恢复。wait/notify 是 Object 类的方法，而 sleep 是 Thread 类的方法。怎么创建线程：继承Thread类，重写run方法，并通过调用start方法启动线程。实现Runnable接口，并重写run方法。使用匿名内部类，或者Lambda表达式，适用于简单的任务。使用线程池，当然不一定会创建线程。class MyThread extends Thread { public void run() { // 线程任务逻辑 } } // 创建并启动线程 MyThread myThread = new MyThread(); myThread.start(); //Runnable class MyRunnable implements Runnable { public void run() { // 线程任务逻辑 } } //Callable MyCallable myCallable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(myCallable); Thread thread = new Thread(futureTask); futureTask.get(); //Runnable Thread thread = new Thread(new MyRunnable()); thread.start(); //匿名内部类 Thread thread = new Thread(new Runnable(){...run()}); //Lambda表达式 Thread thread = new Thread(()->{}); //线程池 ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.submit(()->{});怎么停止线程：使用原子标志位，将逻辑嵌入业务代码。使用interrupt方法，在线程中检测Thread.currentThread().isInterrupted()进行处理。使用stop方法，不推荐，会导致强制停止，可能造成资源泄露。（会释放synchronized而不会自动缩放ReentranLock）如果是线程池，可以使用ExecutorService的shutdown或者shutdownNow。如果线程通过Future启动，可以使用cancel方法取消执行。线程池：参数：参数名称描述corePoolSize线程池中的核心线程数。即使线程处于空闲状态，也会保持在线程池中，除非设置了allowCoreThreadTimeOut。（核心数*（1+等待/运行））maximumPoolSize线程池中允许的最大线程数。当队列满了并且正在运行的线程数小于maximumPoolSize时，会创建新的线程。（可以设置压测值）keepAliveTime当线程数大于核心线程数时，多余的空闲线程在这个时间内没有任务可执行就会被终止。unitkeepAliveTime的时间单位，如秒、分钟等。workQueue用于保存等待执行的任务的阻塞队列。threadFactory每个线程创建都会调用的方法，可以用来设置线程的一些属性，比如是否是守护线程等。handler当线程和队列都满了之后，新提交的任务会触发这个拒绝策略处理器。常用线程池：线程池类型使用场景优点缺点参数FixedThreadPool适用于需要限定并发线程数量的场景，保证了任何时候最多有固定数量的线程同时运行。- 控制资源消耗；- 提供了对线程数量的精确控制。不适合处理大量耗时的任务，因为可能会导致内存溢出。允许队列长度为max intCachedThreadPool适用于执行很多短期异步任务的小程序，或负载较轻的服务器。- 自动回收旧的线程并创建新的线程；- 几乎无限制地创建新线程（注意可能导致内存溢出）。可能会创建大量的线程，导致系统资源耗尽。允许创建线程数为max intScheduledThreadPool适用于需要定时或周期性执行任务的场景。- 支持定时及周期性任务执…