java常用锁机制原理

1. volatile关键字

线程内存模型如下:

由于每个java线程的正常变量在不加锁的情况下, 会在其工作内存中保存备份,从而导致内存数据不一致的情况, volatile能够保证一个基础变量改变和读取不会换成,直接读主内存的功能。

因此此关键字会使用 在某一类线程读写, 另外所有线程只读的单个变量 的场景中

 

 

2. synchronized 关键字

该关键字常用使用方式:

· (1). 对于普通同步方法,锁是当前实例对象。

 

· (2). 对于静态同步方法,锁是当前类的Class对象。

 

· (3). 对于同步方法块,锁是Synchonized括号里配置的对象。

 

锁的标记使用在对象头中, 对象头的内存结构为:

其中Mark Word字段用来支持锁数据, 具体就不细入。

 

synchronized 关键字可能是下面3种锁之一

(1). 偏向锁

(2). 轻量锁

(3). 重量锁

锁升级过程 偏向锁-->轻量锁-->重量锁(升级后不可逆)

 

偏向锁互斥过程:

 

特点:

(1). 获得偏向锁的第一个线程只比非同步方法多几纳秒, 速度极快

(2). 如果存在其他线程竞争, 消耗极大,只有等下一次stop the world的时候才会让先前一个线程释放锁。因此如果只有一个线程使用, 偏向锁作用极大。

 

 

轻量锁, 重量锁互斥过程:

 

注解:

(1). 轻量锁,是对CAS中原子变量的一个限时调用获取锁的过程, 其作用就为自旋锁。如果超过时间,则会自动升级为重量锁, 也就是互斥锁。

(2). 轻量锁使用在一些等待时间特别短并且相对比较稳定的地方, 如: 生产者和消费者中的队列, 插入和获得都比较短,而且较稳定。 和其他单独资源(锁资源程序(不锁逻辑流程))的场景。

(3). 重量锁就是底层的互斥锁, 特点是不过度消耗CPU, 但响应时间缓慢。

 

 

3. 原子操作的实现原理

与原子操作相关的变量有AtomicBoolean(用原子方式更新的boolean值)、AtomicInteger (用原子方式更新的int值)和AtomicLong(用原子方式更 新的long值)

 

常用的操作为compare and swap, 此为传入先前的值和更新的值,如果更新的时候和传入的先前的值不一致,则更新失败。

 

此缺点:

(1). 会有ABA问题, 也就是变量中间被修改了, 但是最后又改回来了,这个时候还会更新成功, 不过一般需求不会有这么苛刻。这个问题可以使用AtomicStampedReference来解决, 此可以支持任何变量(原理为增加版本号信息)。

(2). compare and swap 操作过于耗时, 为自旋操作。

(3). 只能保证一个共享基础变量,如boolean, Interger... 这个可以使用AtomicReference传入对象来使用。

 

 

 

 

 

### Java 中的锁机制及其工作原理 #### 1. 锁机制概述 Java 提供了多种方式来实现并发控制,其中 `synchronized` 和 `ReentrantLock` 是两种最常用锁机制。它们都用于解决多线程环境下的资源竞争问题。 `synchronized` 是一种内置的关键字,提供了隐式的加和解功能[^2]。它通过 JVM 的支持实现了对对象或方法的同步访问。而 `ReentrantLock` 则是一个显式类,位于 `java.util.concurrent.locks` 包下,允许开发人员更灵活地管理的行为[^3]。 --- #### 2. 工作原理 ##### (1) **Synchronized** `synchronized` 关键字的工作原理基于监视器(Monitor)的概念。当一个线程进入被 `synchronized` 修饰的方法或代码块时,会尝试获取该对象的监视器。如果成功,则继续执行;否则,线程会被挂起直到可用并重新调度。 - 对于实例方法,的是当前对象 (`this`)。 - 对于静态方法,的是该类的 `Class` 对象。 JVM 还引入了升级机制以提升性能,具体分为以下几种状态: - **偏向**:适用于只有一个线程访问共享数据的情况。 - **轻量级**:多个线程交替访问但无冲突的情况下使用。 - **重量**:发生真正的线程阻塞时启用。 这些优化减少了不必要的上下文切换开销。 ##### (2) **ReentrantLock** `ReentrantLock` 使用 AQS(AbstractQueuedSynchronizer)作为其核心实现框架。AQS 维护了一个 FIFO 队列结构,用来记录等待获取的线程顺序。 主要流程如下: - 调用 `lock()` 方法请求时,如果没有其他线程持有或者当前线程已经持有了此,则直接授予。 - 如果有其他线程正在占用,则新来的线程会被加入到队列中等待。 - 当前线程完成操作后调用 `unlock()` 方法释放,此时下一个排队的线程可以获得。 此外,`ReentrantLock` 支持设置为公平模式(Fair Lock),从而按照严格的 FIFO 秩序分配资源。 --- #### 3. 实现对比 | 特性 | Synchronized | ReentrantLock | |---------------------|--------------------------------------|-----------------------------------| | 加/解 | 自动 | 手动 | | 可中断 | 否 | 是 | | 公平支持 | 否 | 是 | | 尝试定 | 不提供 | 提供 tryLock | | 性能 | 较低(早期版本可能较慢) | 更高效 | 尽管如此,在大多数场景下推荐优先考虑 `synchronized` ,因为它的语法简洁且不易出错[^1]。 --- #### 4. 并发控制案例 以下是两个简单的例子分别展示如何利用这两种方式进行并发控制: ##### (1) 使用 Synchronized 控制银行账户转账 ```java public class BankAccount { private double balance; public synchronized void deposit(double amount) { // 同步方法 this.balance += amount; } public synchronized void withdraw(double amount) throws Exception{ if(amount > this.balance){ throw new Exception("Insufficient funds"); } this.balance -= amount; } } ``` ##### (2) 使用 ReentrantLock 安全更新计数器 ```java import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment(){ lock.lock(); // 获取 try{ count++; }finally{ lock.unlock(); // 确保最终总是释放 } } public int getCount(){ return count; } } ``` --- #### 5. 注意事项 无论是哪种形式都需要特别注意死风险以及过度依赖带来的性能瓶颈等问题。合理设计程序逻辑能够有效减少这些问题的发生概率。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值