并发(锁)概要整理

并发(锁)概要整理

线程能够在我们项目开发过程中,提供并行处理的能力。尤其是在一些耗时操作上,避免UI主线程的阻塞导致ANR。但在实际使用的过程中,我们也会遇到一些线程问题,例如线程安全,资源占用等问题,在解决这些并发问题上,我们可以充分利用锁这个机制。

概述

锁能够有效的避免一些线程冲突,但为了解决各类问题情景我们也衍生出了各种类型的锁,比如自旋锁,互斥锁、递归锁等等,我们就Android中常用的几种锁来展开理理对应Android来说的一些线程并发问题。

常用关键字

在Android中我们常用以下两种关键字来进行线程锁的实现。

锁名作用域锁类型性质
Synchronized代码块独占锁/排他锁同一个线程执行完之后别的线程才能继续执行,这就是通常说说的 原子性 (线程执行多条指令不被中断)
Volatile单一变量非阻塞算法(也就是不排他)当遇到三行 CPU 指令自然就不能保证别的线程不插足了,这就是通常所说的,volatile 能保证内存可见性,但是不能保证原子性

区别最多的原因是 volatile 禁止了指令重排(cpu内部指令重排序) 而Synchronized没有

ReentrantLock

1、由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:

  • 等待可中断
    持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。
  • 公平锁
    多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
  • 锁绑定多个条件
    一个ReentrantLock对象可以同时绑定对个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

final
只要一个不可变的对象被正确的构建出来(即没有发生this引用逃逸的情况),那其外部的可见状态永远都不会改变,永远都不会看到它在多个线程之中处于不一致的状态。“不可变”带来的安全性是最直接,最纯粹的。

线程安全

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

并发三大特性

  • 可见性
    当一个线程修改了共享变量的值,其他线程能够看到修改的值,Java内存模型是通过在变量修改后将新值同步会主内存,在变量读取前从主内存刷新值,这种依赖主内存作为传递媒介的方法实现可见性
    可以通过以下方案解决
    1、通过 volatile 关键字保证可见性。
    2、通过 内存屏障保证可见性。
    3、通过 synchronized 关键字保证可见性。
    4、通过 Lock保证可见性。
    5、通过 final 关键字保证可见性

  • 有序性
    即程序执行的顺序按照代码的先后顺序执行,JVM存在指令重排,所以存在有序性问题,其实不止是JVM,CPU指令优化器也会重排
    可以通过以下方案解决
    1、通过 volatile 关键字保证有序性
    2、通过内存屏障保证有序性
    3、通过synchronized关键字保证有序性
    4、通过Lock保证有序性

  • 原子性
    一个或多个操作,要么全部执行且执行过程中不被任何因素打扰,要么全部不执行。在Java中,对基本数据类型变量的读取、赋值操作都是原子性的(64位处理器,32位处理器会分段)。不采取任何的原子性保障措施的自增操作并不是原子性的。
    可以通过以下方案解决
    1、通过synchronized关键字保证原子性
    2、通过Lock保证原子性
    3、通过Lock保证原子性

锁的升级

synchronized锁的4种状态
synchronized锁有无锁、偏向锁、轻量级锁和重量级锁4种状态,在对象头的Mark Word里有展示,锁状态不同,Mark Word的结构也不同。
(1)无锁
很好理解,就是不存在竞争,线程没有获取synchronized锁的状态。
(2)偏向锁
即偏向第一个拿到锁的线程,锁会在对象头的Mark Word通过CAS(Compare And Swap)记录获得锁的线程id,同时将Mark Word里的锁状态置为偏向锁,是否为偏向锁的位也置为1,当下一次还是这个线程获取锁时就不需要通过CAS。
如果其他的线程尝试通过CAS获取锁(即想将对象头的Mark Word中的线程ID改成自己的)会获取失败,此时锁由偏向锁升级为轻量级锁。
(3)轻量级锁
JVM会给线程的栈帧中创建一个锁记录(Lock Record)的空间,将对象头的Mark Word拷贝到Lock Record中,并尝试通过CAS把原对象头的Mark Word中指向锁记录的指针指向当前线程中的锁记录,如果成功,表示线程拿到了锁。如果失败,则进行自旋(自旋锁),自旋超过一定次数时升级为重量级锁,这时该线程会被内核挂起。
(4)自旋锁
轻量级锁升级为重量级锁之前,线程执行monitorenter指令进入Monitor对象的EntryList队列,此时会通过自旋尝试获得锁,如果自旋次数超过了一定阈值(默认10),才会升级为重量级锁,等待线程被唤起。
线程等待唤起的过程涉及到Linux系统用户态和内核态的切换,这个过程是很消耗资源的,自选锁的引入正是为了解决这个问题,先不让线程立马进入阻塞状态,而是先给个机会自旋等待一下。
(5)重量级锁
在2中已经介绍,就是通常说的synchronized重量级锁。
锁升级过程

  • 锁升级的顺序为:
    无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,且锁升级的顺序是不可逆的。

线程第一次获取锁获时锁的状态为偏向锁,如果下次还是这个线程获取锁,则锁的状态不变,否则会升级为CAS轻量级锁;如果还有线程竞争获取锁,如果线程获取到了轻量级锁没啥事了,如果没获取到会自旋,自旋期间获取到了锁没啥事,超过了10次还没获取到锁,锁就升级为重量级的锁,此时如果其他线程没获取到重量级锁,就会被阻塞等待唤起,此时效率就低了。

锁降级
  • 锁降级
    当前线程获得写锁,没有释放写锁的情况下再去获得读锁,然后释放写锁,这个过程就是锁降级(当前线程持有锁的状态由写锁降到读锁就是锁降级)
  • 使用场景
    当多线程情况下,更新完数据要立刻查询刚更新完的数据(因更新完数据释放写锁后还持有读锁,所有线程要获得写锁都要等待读锁释放,这时持有读锁的线程可以查到刚更新完的数据)
  • 弊端
    适合读多写少的场景,如果锁降级的同时设置成了非公平锁可能会导致写锁很长时间获得不到

小知识

1、在32位机器上对long型变量进行加减操作是的安全隐患
非 volatile 类型的 long 和 double 型变量是 8 字节 64 位的, 32 位机器读或写这个变量的时候把它们分成两个 32 位操作,可能一个线程读取了某个值的高 32 位,低 32 位被另一个线程修改了。
所以 Java 官方推荐最好把 long/double 变量声明为 volatile 或是同步加锁 synchronized 以避免并发问题
2、ReentrantReadWriteLock支持锁降级,但是不支持锁升级
3、线程安全

  • 线程安全,其实是内存安全,对事共享内存,可以被所有线程访问。
  • 当多个线程访问一个对象,如果不需要额外的控制,调用的找个对象行为都是正确的,通常可以认为是线程安全的;
  • 如果多个线程访问一个对象,每个线程所修改的内容都会覆盖其他线程产生的数据,且对象行为不正确,通常可以认为是非线程安全的;
<think>嗯,用户需要一份技术概要,涵盖计算机基础和Java核心技术点。用户明确列出了IO、多线程、并发、缓存、分布式、Java集合、反射、NIO、JVM原理和GC机制这些关键领域。这看起来像是为面试或技能梳理做的准备。 从引用内容来看,用户参考的几份资料都是Java面试题库,说明用户可能在准备技术面试。引用[1]提到1658页资料覆盖了JVM/多线程等核心内容,引用[2][4]则细分了MySQL/Redis等具体技术的面试题。不过用户当前需求更聚焦在知识体系梳理而非具体题目。 用户的技术要求可以分为两个层面: 1)计算机基础部分需要解释IO模型、线程进程区别、并发控制手段、缓存策略、分布式核心概念 2)Java部分要突出集合框架的设计、NIO的优势、反射机制的风险点、JVM内存结构和GC算法 特别要注意的是,用户提到“整理技术概要”而非深入讲解某个点,所以回复应该保持结构化但不过于深入。每个技术点用一两句话说明核心即可,比如解释BIO/NIO区别时用“阻塞vs非阻塞”这样的关键词。 在组织内容时,我会把计算机基础和Java技术分成两大块。计算机基础按IO->多线程->并发->缓存->分布式的逻辑链条排列,Java部分则按集合->IO->反射->NIO->JVM的顺序,这样符合从基础到高级的认知规律。JVM作为重点单独列出,因为用户特别强调了要“熟悉原理和GC机制”。 最后考虑补充相关问题,帮助用户自我检测。问题设计要覆盖所有技术点但避免重复,比如: - 分布式的实现方式(对应分布式章节) - ConcurrentHashMap的并发机制(衔接Java集合和并发) - G1收集器特点(深化JVM知识点) 这样既能检验知识掌握,又体现技术点之间的关联性。</think>以下是根据您的要求整理的技术概要,涵盖计算机基础知识和Java核心技术栈,结合主流面试考点和实际应用场景: ### 一、计算机基础核心 1. **I/O模型** - 阻塞/非阻塞I/O:BIO vs NIO - 多路复用:`select/poll/epoll`原理(Linux) - 异步I/O:`AIO`实现原理 - 应用场景:高并发网络服务器设计[^4] 2. **多线程与并发** - 线程生命周期:新建、就绪、运行、阻塞、终止 - 线程同步机制: ```java synchronized // 对象 ReentrantLock // 可重入 Semaphore // 信号量 ``` - 并发容器:`ConcurrentHashMap`分段,`CopyOnWriteArrayList`写时复制[^2] 3. **分布式系统** - CAP定理:一致性、可用性、分区容错性权衡 - 分布式事务:2PC、TCC、Saga模式 - 服务治理:熔断(Hystrix)、限流(Sentinel)[^4] 4. **缓存技术** - 缓存穿透:布隆过滤器解决方案 - 缓存雪崩:随机过期时间+多级缓存 - Redis核心:单线程Reactor模型、持久化(RDB/AOF)[^2][^4] ### 二、Java核心技术栈 1. **集合框架** - `HashMap`:数组+链表/红黑树(JDK8+),负载因子0.75 - `ConcurrentHashMap`:分段(JDK7) vs CAS+synchronized(JDK8) - `ArrayList` vs `LinkedList`:随机访问$O(1)$ vs 插入删除$O(1)$[^1][^2] 2. **I/NIO** - BIO:线程-per-connection模型(`ServerSocket`) - NIO:Channel/Buffer/Selector三件套 ```java Selector selector = Selector.open(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); ``` - Netty:基于事件驱动的Reactor模式实现[^4] 3. **反射机制** - 动态代理:`Proxy.newProxyInstance()`生成代理对象 - 应用场景:Spring AOP、MyBatis Mapper映射 - 性能隐患:直接调用比反射快100+倍[^2] 4. **JVM原理** - 内存模型: ``` ┌─────────┐ ┌──────────┐ ┌───────┐ │ 方法区 │ │ 堆内存 │ │ 栈内存 │ └─────────┘ └──────────┘ └───────┘ ``` - 类加载:加载→验证→准备→解析→初始化 - GC算法: - 标记-清除(老年代CMS) - 复制算法(新生代) - G1:分Region收集+预测停顿时间[^1][^4] 5. **GC调优** - 监控工具:`jstat -gcutil`、VisualVM - 参数示例: ``` -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200 ``` - OOM排查:MAT分析堆转储文件[^4] --- ### 三、技术关联场景 | 技术组合 | 应用场景 | |--------------------|--------------------------| | NIO+多线程+Redis | 电商秒杀系统 | | JVM调优+GC算法 | 高吞吐量金融交易系统 | | 分布式+缓存 | 社交平台热点数据推送 | --- ### 相关问题 1. 如何设计一个支持10万并发的NIO服务器? 2. `ConcurrentHashMap`在JDK7和JDK8中的实现差异是什么? 3. G1收集器如何实现可预测的停顿时间? 4. Redis缓存穿透有哪些解决方案? 5. 分布式事务中TCC模式的实现原理是什么? > 参考资料: > [^1]: Java面试核心技术栈:JVM/多线程/集合框架等底层原理 > [^2]: 分布式/缓存/高并发场景解决方案 > [^4]: JVM调优实战与性能监控工具解析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值