Java技术八股学习Day14

乐观锁和悲观锁的定义

乐观锁假设多线程访问共享资源时冲突概率极低,核心思想是 "先操作后校验",不加锁直接执行操作,仅在提交时通过特定机制(如 CAS、版本号)判断资源是否被修改,若未修改则执行成功,否则重试或返回失败;悲观锁则假设冲突必然发生,核心思想是 "先加锁再操作",访问资源前先获取锁,确保同一时间仅一个线程能操作资源,其他线程需等待锁释放,本质是通过独占锁规避冲突。

乐观锁和悲观锁的核心区别

两者核心差异集中在冲突假设、实现方式、性能开销和适用场景:乐观锁基于 "冲突少" 假设,无锁设计,依赖 CAS 或版本号校验,并发性能高、无死锁风险,但存在重试开销和 ABA 问题;悲观锁基于 "冲突多" 假设,通过独占锁(如 synchronized、数据库行锁)实现,能直接规避冲突,无需重试,但锁竞争会导致线程阻塞,存在死锁风险,并发性能较低。

悲观锁的常见实现方式

悲观锁的实现分为 Java 层面和数据库层面:Java 中主要通过synchronized关键字(隐式锁,自动释放)、ReentrantLock类(显式锁,需手动释放)实现,二者均为独占锁,能保证同一时间仅一个线程执行临界区代码;数据库中主要是行锁(如 InnoDB 的行级锁)、表锁(如 MyISAM 的表级锁),通过 SQL 执行时自动加锁或手动加锁(如select ... for update),确保数据操作的原子性。

乐观锁的常见实现方式

乐观锁无实际 "锁" 的概念,核心通过 "校验机制" 实现,主要有两种方式:一是 CAS(Compare and Swap,比较并交换),底层依赖 CPU 原子指令,需传入内存地址、预期值、新值三个参数,仅当内存中实际值与预期值一致时才替换为新值,Java 中AtomicIntegerAtomicReference等原子类均基于 CAS 实现;二是版本号机制,数据库场景中常用,给表新增version字段(或时间戳字段),更新数据时对比版本号(如update table set ... where id=? and version=?),一致则更新并自增版本号,不一致则说明资源被修改,需重试或返回失败。

CAS 的原理与存在的问题

CAS 的核心是 CPU 提供的原子指令(如 x86 的cmpxchg),能保证 "比较 - 替换" 操作的原子性,避免并发问题;Java 中的 CAS 通过Unsafe类直接调用底层 native 方法实现,依赖硬件支持。CAS 存在三个核心问题:一是 ABA 问题(内存值从 A 变为 B 再变回 A,CAS 误判为未修改);二是循环时间长开销大(自旋 CAS 失败后会持续重试,高冲突场景下占用大量 CPU 资源);三是只能保证单个变量的原子操作,无法直接实现多个变量的原子性组合操作。

IO 流简介

IO(Input/Output)即输入与输出,数据输入到计算机内存的过程为输入,输出到外部存储(如文件、数据库)的过程为输出,数据传输类似水流故称为 IO 流。Java IO 流分为输入流与输出流,按数据处理方式又分为字节流(处理原始字节)和字符流(处理字符,避免乱码),所有 IO 流类均派生自四个抽象基类:字节输入流基类InputStream、字符输入流基类Reader、字节输出流基类OutputStream、字符输出流基类Writer

字节流

字节流直接操作原始字节数据,适用于音频、图片等媒体文件。InputStream是所有字节输入流的父类,提供read()(读字节)、skip()(跳过字节)、close()(关闭流)等方法,Java9 后新增readAllBytes()(读所有字节)、transferTo()(传输到输出流)等实用方法,常用实现类有FileInputStream(直接读取文件字节)、DataInputStream(读取指定类型数据,需结合其他流)、ObjectInputStream(反序列化读取 Java 对象,类需实现Serializable接口,transient修饰属性不序列化)。OutputStream是所有字节输出流的父类,提供write()(写字节)、flush()(刷新缓冲)、close()(关闭流)等方法,常用实现类有FileOutputStream(直接写入文件字节)、DataOutputStream(写入指定类型数据)、ObjectOutputStream(序列化写入 Java 对象)。

字符流

字符流由 JVM 将字节转换为字符,默认采用 Unicode 编码,可自定义编码,适用于文本文件,避免字节流处理字符时的乱码问题(字节流无编码感知)。Reader是所有字符输入流的父类,提供read()(读字符)、skip()(跳过字符)等方法,常用实现类有InputStreamReader(字节流转字符流的桥梁)、FileReader(封装InputStreamReader,直接读取字符文件)。Writer是所有字符输出流的父类,提供write()(写字符 / 字符串)、append()(附加字符)、flush()(刷新缓冲)等方法,常用实现类有OutputStreamWriter(字符流转字节流的桥梁)、FileWriter(封装OutputStreamWriter,直接写入字符到文件)。

缓冲流

IO 操作消耗性能,缓冲流通过内部缓冲区(字节数组)一次性读取 / 写入多个字节,减少频繁 IO 操作,提高传输效率,采用装饰器模式增强普通流功能。字节缓冲流包括BufferedInputStream(增强字节输入流)和BufferedOutputStream(增强字节输出流),默认缓冲区大小 8192 字节,可自定义;字符缓冲流包括BufferedReader(增强字符输入流)和BufferedWriter(增强字符输出流),内部同样维护缓冲区。当使用单字节 / 字符读写方法时,缓冲流与普通流性能差距极大(缓冲流耗时仅为普通流的 1/165 左右);使用字节数组 / 字符数组读写时,两者性能差距较小。

打印流

打印流用于便捷输出数据,分为字节打印流PrintStream和字符打印流PrintWriterPrintStreamOutputStream的子类,System.out本质是PrintStream对象,print()/println()方法底层调用其write()方法;PrintWriterWriter的子类,功能与PrintStream类似,更适合处理字符数据。

随机访问流

随机访问流RandomAccessFile支持跳转到文件任意位置读写,需指定读写模式(r只读、rw读写、rws同步更新内容和元数据、rwd同步更新内容)。其内部通过文件指针标记下一个读写位置,可通过seek(long pos)设置指针偏移量,getFilePointer()获取当前指针位置,写入数据时会覆盖目标位置原有数据。常见应用为大文件断点续传(基于文件分片,上传失败后仅传未成功分片),可通过该类合并文件分片。

相关推荐
橙露几秒前
数据库运维核心:MySQL主从复制与读写分离的部署与维护
运维·数据库·mysql
辉辉要奋斗4 分钟前
MySQL安装出现This application requires Visual Studio 2019 x64Redistributable,Please install the Redistri
数据库·mysql·visual studio
码农水水5 分钟前
SpringBoot配置优化:Tomcat+数据库+缓存+日志全场景教程
java·数据库·spring boot·后端·算法·tomcat·哈希算法
毕设源码-朱学姐5 分钟前
【开题答辩全过程】以 基于ssm的电影推荐与分享平台的设计与实现为例,包含答辩的问题和答案
java
独自破碎E9 分钟前
LCR004-只出现一次的数字II
java·开发语言
爱学习的阿磊11 分钟前
Python迭代器(Iterator)揭秘:for循环背后的故事
jvm·数据库·python
Elias不吃糖13 分钟前
Spring Bean 注入与容器管理:从“怎么交给容器”到“怎么被注入使用”的完整总结
java·spring·rpc·bean
宇钶宇夕20 分钟前
CoDeSys入门实战一起学习(二十五):梯形图(LD)触点与线圈指令精讲及电机启停案例
运维·学习·自动化·软件工程
Chan1620 分钟前
《Redis设计与实现》| 常用数据类型与AOF、RDB持久化
java·开发语言·redis·spring·面试·java-ee
wljt21 分钟前
游标分页原理
java·前端·数据库